launcher: apps mode

This commit is contained in:
2 * r + 2 * t 2025-02-28 12:48:42 +11:00
parent 73e7c08b8c
commit 5cf36b0058
3 changed files with 136 additions and 17 deletions

View file

@ -1,3 +1,4 @@
@use "sass:color";
@use "scheme"; @use "scheme";
@use "lib"; @use "lib";
@use "font"; @use "font";
@ -14,8 +15,19 @@
} }
} }
.mode-switcher .mode.selected { .mode-switcher .mode {
border-top: lib.s(2) solid $colour; &.selected {
border-top: lib.s(2) solid $colour;
}
&:hover,
&:focus {
color: $colour;
}
&:active {
color: color.mix($colour, scheme.$base, 80%);
}
} }
} }
} }
@ -32,14 +44,14 @@
min-width: lib.s(700); min-width: lib.s(700);
min-height: lib.s(420); min-height: lib.s(420);
@include lib.spacing(20, true);
@include launcher(apps, scheme.$sapphire); @include launcher(apps, scheme.$sapphire);
@include launcher(files, scheme.$peach); @include launcher(files, scheme.$peach);
@include launcher(math, scheme.$green); @include launcher(math, scheme.$green);
@include launcher(windows, scheme.$teal); @include launcher(windows, scheme.$teal);
.search-bar { .search-bar {
margin-bottom: lib.s(15);
@include lib.spacing(10); @include lib.spacing(10);
.mode { .mode {
@ -55,23 +67,61 @@
border-top: lib.s(2) solid transparent; border-top: lib.s(2) solid transparent;
padding-top: lib.s(10); padding-top: lib.s(10);
.icon {
font-size: lib.s(24);
}
& > box {
@include lib.spacing(10);
}
} }
.result { .result {
@include lib.element-decel; @include lib.element-decel;
padding: lib.s(5) lib.s(10); .icon {
font-size: lib.s(32);
}
}
&:hover, .apps .result {
&:focus { border-bottom: lib.s(2) solid transparent;
padding-left: lib.s(10);
& > box {
@include lib.spacing(10);
}
&:hover {
background-color: scheme.$surface0; background-color: scheme.$surface0;
} }
&:focus {
border-bottom: lib.s(2) solid scheme.$red;
color: scheme.$red;
}
&:active { &:active {
background-color: scheme.$surface1; background-color: scheme.$surface1;
} }
} }
// .result {
// @include lib.element-decel;
// padding: lib.s(5) lib.s(10);
// &:hover,
// &:focus {
// background-color: scheme.$surface0;
// }
// &:active {
// background-color: scheme.$surface1;
// }
// }
.pinned-app { .pinned-app {
@include lib.rounded(5); @include lib.rounded(5);

View file

@ -1,6 +1,11 @@
import { Apps as AppsService } from "@/services/apps";
import { getAppCategoryIcon } from "@/utils/icons";
import { launch } from "@/utils/system";
import { FlowBox } from "@/utils/widgets";
import PopupWindow from "@/widgets/popupwindow"; import PopupWindow from "@/widgets/popupwindow";
import { bind, register, Variable } from "astal"; import { bind, register, Variable } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3"; import { App, Astal, Gtk, Widget } from "astal/gtk3";
import type AstalApps from "gi://AstalApps";
type Mode = "apps" | "files" | "math" | "windows"; type Mode = "apps" | "files" | "math" | "windows";
@ -9,25 +14,77 @@ interface ModeContent {
handleActivate(search: string): void; handleActivate(search: string): void;
} }
const close = () => App.get_window("launcher")?.hide();
const getModeIcon = (mode: Mode) => {
if (mode === "apps") return "apps";
if (mode === "files") return "folder";
if (mode === "math") return "calculate";
if (mode === "windows") return "select_window";
return "search";
};
const getPrettyMode = (mode: Mode) => {
if (mode === "apps") return "Apps";
if (mode === "files") return "Files";
if (mode === "math") return "Math";
if (mode === "windows") return "Windows";
return mode;
};
const AppResult = ({ app }: { app: AstalApps.Application }) => (
<Gtk.FlowBoxChild visible canFocus={false}>
<button
className="result"
cursor="pointer"
onClicked={() => {
launch(app);
close();
}}
>
<box>
{app.iconName && Astal.Icon.lookup_icon(app.iconName) ? (
<icon className="icon" icon={app.iconName} />
) : (
<label className="icon" label={getAppCategoryIcon(app)} />
)}
<label truncate label={app.name} />
</box>
</button>
</Gtk.FlowBoxChild>
);
@register() @register()
class Apps extends Widget.Box implements ModeContent { class Apps extends Widget.Box implements ModeContent {
#content: FlowBox;
constructor() { constructor() {
super({ name: "apps" }); super({ name: "apps", className: "apps" });
this.#content = (<FlowBox homogeneous valign={Gtk.Align.START} maxChildrenPerLine={2} />) as FlowBox;
this.add(
<scrollable expand hscroll={Gtk.PolicyType.NEVER}>
{this.#content}
</scrollable>
);
} }
updateContent(search: string): void { updateContent(search: string): void {
throw new Error("Method not implemented."); this.#content.foreach(c => c.destroy());
AppsService.fuzzy_query(search).forEach(app => this.#content.add(<AppResult app={app} />));
} }
handleActivate(search: string): void { handleActivate(): void {
throw new Error("Method not implemented."); this.#content.get_child_at_index(0)?.get_child()?.grab_focus();
this.#content.get_child_at_index(0)?.get_child()?.activate();
} }
} }
@register() @register()
class Files extends Widget.Box implements ModeContent { class Files extends Widget.Box implements ModeContent {
constructor() { constructor() {
super({ name: "files" }); super({ name: "files", className: "files" });
} }
updateContent(search: string): void { updateContent(search: string): void {
@ -42,7 +99,7 @@ class Files extends Widget.Box implements ModeContent {
@register() @register()
class Math extends Widget.Box implements ModeContent { class Math extends Widget.Box implements ModeContent {
constructor() { constructor() {
super({ name: "math" }); super({ name: "math", className: "math" });
} }
updateContent(search: string): void { updateContent(search: string): void {
@ -57,7 +114,7 @@ class Math extends Widget.Box implements ModeContent {
@register() @register()
class Windows extends Widget.Box implements ModeContent { class Windows extends Widget.Box implements ModeContent {
constructor() { constructor() {
super({ name: "windows" }); super({ name: "windows", className: "windows" });
} }
updateContent(search: string): void { updateContent(search: string): void {
@ -83,8 +140,12 @@ const ModeSwitcher = ({ mode, modes }: { mode: Variable<Mode>; modes: Mode[] })
className={bind(mode).as(c => `mode ${c === m ? "selected" : ""}`)} className={bind(mode).as(c => `mode ${c === m ? "selected" : ""}`)}
cursor="pointer" cursor="pointer"
onClicked={() => mode.set(m)} onClicked={() => mode.set(m)}
label={m} >
/> <box halign={Gtk.Align.CENTER}>
<label className="icon" label={getModeIcon(m)} />
<label label={getPrettyMode(m)} />
</box>
</button>
))} ))}
</box> </box>
); );
@ -144,6 +205,7 @@ export default class Launcher extends PopupWindow {
this.mode = mode; this.mode = mode;
content[mode.get()].updateContent(entry.get_text());
this.hook(mode, (_, v: Mode) => { this.hook(mode, (_, v: Mode) => {
entry.set_text(""); entry.set_text("");
content[v].updateContent(entry.get_text()); content[v].updateContent(entry.get_text());

View file

@ -73,3 +73,10 @@ export class Calendar extends astalify(Gtk.Calendar) {
super(props as any); super(props as any);
} }
} }
@register()
export class FlowBox extends astalify(Gtk.FlowBox) {
constructor(props: ConstructProps<FlowBox, Gtk.FlowBox.ConstructorProps>) {
super(props as any);
}
}