feat(ags4): start subclassing widgets
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
dc410ebf59
commit
362bc7314d
8 changed files with 357 additions and 42 deletions
|
@ -2,7 +2,7 @@ import { App } from 'astal/gtk4';
|
||||||
|
|
||||||
import style from './style.scss';
|
import style from './style.scss';
|
||||||
|
|
||||||
import Bar from './widget/Bar';
|
import Bar from './widget/bar';
|
||||||
|
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import { App, Astal, Gtk } from 'astal/gtk4';
|
import { App, Astal, Gtk } from 'astal/gtk4';
|
||||||
import { Variable } from 'astal';
|
import { Variable } from 'astal';
|
||||||
|
|
||||||
import { StyledBox } from './styled-box';
|
import { Box, Button, CenterBox } from './subclasses';
|
||||||
|
|
||||||
const { EXCLUSIVE } = Astal.Exclusivity;
|
const { EXCLUSIVE } = Astal.Exclusivity;
|
||||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
|
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
|
||||||
const { CENTER } = Gtk.Align;
|
const { CENTER } = Gtk.Align;
|
||||||
|
|
||||||
|
|
||||||
const time = Variable(0);
|
const time = Variable(0);
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
@ -16,10 +15,10 @@ setInterval(() => {
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const styledBox = (
|
const styledBox = (
|
||||||
<StyledBox
|
<Box
|
||||||
css={time().as((t) => `* { background: red; min-height: 10px; min-width: ${t}px; }`)}
|
css={time().as((t) => `* { background: red; min-height: 10px; min-width: ${t}px; }`)}
|
||||||
/>
|
/>
|
||||||
) as StyledBox;
|
) as Box;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<window
|
<window
|
||||||
|
@ -29,8 +28,8 @@ export default () => {
|
||||||
anchor={TOP | LEFT | RIGHT}
|
anchor={TOP | LEFT | RIGHT}
|
||||||
application={App}
|
application={App}
|
||||||
>
|
>
|
||||||
<centerbox cssName="centerbox">
|
<CenterBox cssName="centerbox">
|
||||||
<box />
|
<Button onClicked="echo hi" />
|
||||||
|
|
||||||
{styledBox}
|
{styledBox}
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ export default () => {
|
||||||
<Gtk.Calendar />
|
<Gtk.Calendar />
|
||||||
</popover>
|
</popover>
|
||||||
</menubutton>
|
</menubutton>
|
||||||
</centerbox>
|
</CenterBox>
|
||||||
</window>
|
</window>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -1,34 +0,0 @@
|
||||||
import { astalify, Gtk } from 'astal/gtk4';
|
|
||||||
import { property, register } from 'astal';
|
|
||||||
|
|
||||||
|
|
||||||
@register({ GTypeName: 'StyledBox' })
|
|
||||||
class StyledBoxClass extends Gtk.Box {
|
|
||||||
declare private _css: string | undefined;
|
|
||||||
declare private _provider: Gtk.CssProvider | undefined;
|
|
||||||
|
|
||||||
@property(String)
|
|
||||||
get css(): string | undefined {
|
|
||||||
return this._css;
|
|
||||||
}
|
|
||||||
|
|
||||||
set css(value: string) {
|
|
||||||
if (!this._provider) {
|
|
||||||
this._provider = new Gtk.CssProvider();
|
|
||||||
|
|
||||||
this.get_style_context().add_provider(
|
|
||||||
this._provider,
|
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_USER,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._css = value;
|
|
||||||
this._provider.load_from_string(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StyledBox = StyledBoxClass;
|
|
||||||
export const StyledBox = astalify<
|
|
||||||
StyledBoxClass,
|
|
||||||
Gtk.Box.ConstructorProps & { css: string }
|
|
||||||
>(StyledBoxClass);
|
|
272
modules/ags/gtk4/widget/subclasses/astalify.ts
Normal file
272
modules/ags/gtk4/widget/subclasses/astalify.ts
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import Gdk from 'gi://Gdk?version=4.0';
|
||||||
|
import GObject from 'gi://GObject';
|
||||||
|
import Gtk from 'gi://Gtk?version=4.0';
|
||||||
|
|
||||||
|
import Binding, { type Connectable, type Subscribable } from 'astal/binding';
|
||||||
|
import {
|
||||||
|
hook,
|
||||||
|
noImplicitDestroy,
|
||||||
|
setChildren,
|
||||||
|
construct,
|
||||||
|
} from 'astal/_astal';
|
||||||
|
|
||||||
|
export type BindableChild = Gtk.Widget | Binding<Gtk.Widget>;
|
||||||
|
|
||||||
|
export const filter = (children: any[]) => {
|
||||||
|
return children.flat(Infinity).map((ch) => ch instanceof Gtk.Widget ?
|
||||||
|
ch :
|
||||||
|
new Gtk.Label({ visible: true, label: String(ch) }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const dummyBuilder = new Gtk.Builder();
|
||||||
|
const type = Symbol('child type');
|
||||||
|
|
||||||
|
interface EventController<Self extends Gtk.Widget> {
|
||||||
|
onFocusEnter?: (self: Self) => void
|
||||||
|
onFocusLeave?: (self: Self) => void
|
||||||
|
|
||||||
|
onKeyPressed?: (self: Self, keyval: number, keycode: number, state: Gdk.ModifierType) => void
|
||||||
|
onKeyReleased?: (self: Self, keyval: number, keycode: number, state: Gdk.ModifierType) => void
|
||||||
|
onKeyModifier?: (self: Self, state: Gdk.ModifierType) => void
|
||||||
|
|
||||||
|
onLegacy?: (self: Self, event: Gdk.Event) => void
|
||||||
|
onButtonPressed?: (self: Self, state: Gdk.ButtonEvent) => void
|
||||||
|
onButtonReleased?: (self: Self, state: Gdk.ButtonEvent) => void
|
||||||
|
|
||||||
|
onHoverEnter?: (self: Self, x: number, y: number) => void
|
||||||
|
onHoverLeave?: (self: Self) => void
|
||||||
|
onMotion?: (self: Self, x: number, y: number) => void
|
||||||
|
|
||||||
|
onScroll?: (self: Self, dx: number, dy: number) => void
|
||||||
|
onScrollDecelerate?: (self: Self, vel_x: number, vel_y: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupControllers = <T>(widget: Gtk.Widget, {
|
||||||
|
onFocusEnter,
|
||||||
|
onFocusLeave,
|
||||||
|
onKeyPressed,
|
||||||
|
onKeyReleased,
|
||||||
|
onKeyModifier,
|
||||||
|
onLegacy,
|
||||||
|
onButtonPressed,
|
||||||
|
onButtonReleased,
|
||||||
|
onHoverEnter,
|
||||||
|
onHoverLeave,
|
||||||
|
onMotion,
|
||||||
|
onScroll,
|
||||||
|
onScrollDecelerate,
|
||||||
|
...props
|
||||||
|
}: EventController<Gtk.Widget> & T) => {
|
||||||
|
if (onFocusEnter || onFocusLeave) {
|
||||||
|
const focus = new Gtk.EventControllerFocus();
|
||||||
|
|
||||||
|
widget.add_controller(focus);
|
||||||
|
|
||||||
|
if (onFocusEnter) { focus.connect('focus-enter', () => onFocusEnter(widget)); }
|
||||||
|
|
||||||
|
if (onFocusLeave) { focus.connect('focus-leave', () => onFocusLeave(widget)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onKeyPressed || onKeyReleased || onKeyModifier) {
|
||||||
|
const key = new Gtk.EventControllerKey();
|
||||||
|
|
||||||
|
widget.add_controller(key);
|
||||||
|
|
||||||
|
if (onKeyPressed) {
|
||||||
|
key.connect('key-pressed', (_, val, code, state) => onKeyPressed(widget, val, code, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onKeyReleased) {
|
||||||
|
key.connect('key-released', (_, val, code, state) =>
|
||||||
|
onKeyReleased(widget, val, code, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onKeyModifier) { key.connect('modifiers', (_, state) => onKeyModifier(widget, state)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onLegacy || onButtonPressed || onButtonReleased) {
|
||||||
|
const legacy = new Gtk.EventControllerLegacy();
|
||||||
|
|
||||||
|
widget.add_controller(legacy);
|
||||||
|
|
||||||
|
legacy.connect('event', (_, event) => {
|
||||||
|
if (event.get_event_type() === Gdk.EventType.BUTTON_PRESS) {
|
||||||
|
onButtonPressed?.(widget, event as Gdk.ButtonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.get_event_type() === Gdk.EventType.BUTTON_RELEASE) {
|
||||||
|
onButtonReleased?.(widget, event as Gdk.ButtonEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLegacy?.(widget, event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onMotion || onHoverEnter || onHoverLeave) {
|
||||||
|
const hover = new Gtk.EventControllerMotion();
|
||||||
|
|
||||||
|
widget.add_controller(hover);
|
||||||
|
|
||||||
|
if (onHoverEnter) { hover.connect('enter', (_, x, y) => onHoverEnter(widget, x, y)); }
|
||||||
|
|
||||||
|
if (onHoverLeave) { hover.connect('leave', () => onHoverLeave(widget)); }
|
||||||
|
|
||||||
|
if (onMotion) { hover.connect('motion', (_, x, y) => onMotion(widget, x, y)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onScroll || onScrollDecelerate) {
|
||||||
|
const scroll = new Gtk.EventControllerScroll();
|
||||||
|
|
||||||
|
widget.add_controller(scroll);
|
||||||
|
|
||||||
|
if (onScroll) { scroll.connect('scroll', (_, x, y) => onScroll(widget, x, y)); }
|
||||||
|
|
||||||
|
if (onScrollDecelerate) {
|
||||||
|
scroll.connect('decelerate', (_, x, y) => onScrollDecelerate(widget, x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default <
|
||||||
|
C extends new(...args: any[]) => Gtk.Widget,
|
||||||
|
>(cls: C, clsName = cls.name) => {
|
||||||
|
class Widget extends cls {
|
||||||
|
declare private _css: string | undefined;
|
||||||
|
declare private _provider: Gtk.CssProvider | undefined;
|
||||||
|
|
||||||
|
get css(): string | undefined {
|
||||||
|
return this._css;
|
||||||
|
}
|
||||||
|
|
||||||
|
set css(value: string) {
|
||||||
|
if (!this._provider) {
|
||||||
|
this._provider = new Gtk.CssProvider();
|
||||||
|
|
||||||
|
this.get_style_context().add_provider(
|
||||||
|
this._provider,
|
||||||
|
Gtk.STYLE_PROVIDER_PRIORITY_USER,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._css = value;
|
||||||
|
this._provider.load_from_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
declare private [type]: string;
|
||||||
|
get type(): string { return this[type]; }
|
||||||
|
set type(value: string) { this[type] = value; }
|
||||||
|
|
||||||
|
get children(): Gtk.Widget[] { return this.getChildren(this); }
|
||||||
|
set children(value: Gtk.Widget[]) { this.setChildren(this, value); }
|
||||||
|
|
||||||
|
|
||||||
|
declare private [noImplicitDestroy]: boolean;
|
||||||
|
get noImplicitDestroy(): boolean { return this[noImplicitDestroy]; }
|
||||||
|
set noImplicitDestroy(value: boolean) { this[noImplicitDestroy] = value; }
|
||||||
|
|
||||||
|
protected getChildren(widget: Gtk.Widget): Gtk.Widget[] {
|
||||||
|
if ('get_child' in widget && typeof widget.get_child == 'function') {
|
||||||
|
return widget.get_child() ? [widget.get_child()] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const children: Gtk.Widget[] = [];
|
||||||
|
let ch = widget.get_first_child();
|
||||||
|
|
||||||
|
while (ch !== null) {
|
||||||
|
children.push(ch);
|
||||||
|
ch = ch.get_next_sibling();
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setChildren(widget: Gtk.Widget, children: any[]) {
|
||||||
|
children = children.flat(Infinity).map((ch) => ch instanceof Gtk.Widget ?
|
||||||
|
ch :
|
||||||
|
new Gtk.Label({ visible: true, label: String(ch) }));
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
widget.vfunc_add_child(
|
||||||
|
dummyBuilder,
|
||||||
|
child,
|
||||||
|
type in widget ? widget[type] as string : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[setChildren](children: any[]) {
|
||||||
|
const w = this as unknown as Widget;
|
||||||
|
|
||||||
|
for (const child of (this.getChildren(w))) {
|
||||||
|
if (child instanceof Gtk.Widget) {
|
||||||
|
child.unparent();
|
||||||
|
if (!children.includes(child) && noImplicitDestroy in this) { child.run_dispose(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setChildren(w, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
hook(
|
||||||
|
object: Connectable,
|
||||||
|
signal: string,
|
||||||
|
callback: (self: this, ...args: any[]) => void,
|
||||||
|
): this;
|
||||||
|
hook(
|
||||||
|
object: Subscribable,
|
||||||
|
callback: (self: this, ...args: any[]) => void,
|
||||||
|
): this;
|
||||||
|
hook(
|
||||||
|
object: Connectable | Subscribable,
|
||||||
|
signalOrCallback: string | ((self: this, ...args: any[]) => void),
|
||||||
|
callback?: (self: this, ...args: any[]) => void,
|
||||||
|
) {
|
||||||
|
hook(this, object, signalOrCallback, callback);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(...params: any[]) {
|
||||||
|
const props = params[0] || {};
|
||||||
|
|
||||||
|
super('cssName' in props ? { cssName: props.cssName } : {});
|
||||||
|
|
||||||
|
if ('cssName' in props) {
|
||||||
|
delete props.cssName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.noImplicitDestroy) {
|
||||||
|
this.noImplicitDestroy = true;
|
||||||
|
delete props.noImplicitDestroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.type) {
|
||||||
|
this.type = props.type;
|
||||||
|
delete props.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
construct(this as any, setupControllers(this, props));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GObject.registerClass({
|
||||||
|
GTypeName: `RealClass_${clsName}`,
|
||||||
|
Properties: {
|
||||||
|
'css': GObject.ParamSpec.string(
|
||||||
|
'css', '', '', GObject.ParamFlags.READWRITE, '',
|
||||||
|
),
|
||||||
|
'type': GObject.ParamSpec.string(
|
||||||
|
'type', '', '', GObject.ParamFlags.READWRITE, '',
|
||||||
|
),
|
||||||
|
'no-implicit-destroy': GObject.ParamSpec.boolean(
|
||||||
|
'no-implicit-destroy', '', '', GObject.ParamFlags.READWRITE, false,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}, Widget);
|
||||||
|
|
||||||
|
return Widget;
|
||||||
|
};
|
25
modules/ags/gtk4/widget/subclasses/box.ts
Normal file
25
modules/ags/gtk4/widget/subclasses/box.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { register } from 'astal';
|
||||||
|
import { Astal, type ConstructProps } from 'astal/gtk4';
|
||||||
|
|
||||||
|
import astalify, { filter } from './astalify';
|
||||||
|
|
||||||
|
|
||||||
|
export type BoxProps = ConstructProps<
|
||||||
|
Box,
|
||||||
|
Astal.Box.ConstructorProps & { css: string }
|
||||||
|
>;
|
||||||
|
|
||||||
|
@register({ GTypeName: 'Box' })
|
||||||
|
export class Box extends astalify(Astal.Box) {
|
||||||
|
constructor(props?: BoxProps) { super(props as any); }
|
||||||
|
|
||||||
|
getChildren(self: Box) {
|
||||||
|
return self.get_children();
|
||||||
|
}
|
||||||
|
|
||||||
|
setChildren(self: Box, children: any[]) {
|
||||||
|
return self.set_children(filter(children));
|
||||||
|
}
|
||||||
|
}
|
21
modules/ags/gtk4/widget/subclasses/button.ts
Normal file
21
modules/ags/gtk4/widget/subclasses/button.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { register } from 'astal';
|
||||||
|
import { Gtk, type ConstructProps } from 'astal/gtk4';
|
||||||
|
|
||||||
|
import astalify from './astalify';
|
||||||
|
|
||||||
|
|
||||||
|
type ButtonSignals = Record<`on${string}`, unknown[]> & {
|
||||||
|
onClicked: []
|
||||||
|
};
|
||||||
|
export type ButtonProps = ConstructProps<
|
||||||
|
Button,
|
||||||
|
Gtk.Button.ConstructorProps & { css: string },
|
||||||
|
ButtonSignals
|
||||||
|
>;
|
||||||
|
|
||||||
|
@register({ GTypeName: 'Button' })
|
||||||
|
export class Button extends astalify(Gtk.Button) {
|
||||||
|
constructor(props?: ButtonProps) { super(props as any); }
|
||||||
|
}
|
29
modules/ags/gtk4/widget/subclasses/centerbox.ts
Normal file
29
modules/ags/gtk4/widget/subclasses/centerbox.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { register } from 'astal';
|
||||||
|
import { Gtk, type ConstructProps } from 'astal/gtk4';
|
||||||
|
|
||||||
|
import astalify, { filter } from './astalify';
|
||||||
|
|
||||||
|
|
||||||
|
export type CenterBoxProps = ConstructProps<
|
||||||
|
CenterBox,
|
||||||
|
Gtk.CenterBox.ConstructorProps & { css: string }
|
||||||
|
>;
|
||||||
|
|
||||||
|
@register({ GTypeName: 'CenterBox' })
|
||||||
|
export class CenterBox extends astalify(Gtk.CenterBox) {
|
||||||
|
constructor(props?: CenterBoxProps) { super(props as any); }
|
||||||
|
|
||||||
|
getChildren(box: CenterBox) {
|
||||||
|
return [box.startWidget, box.centerWidget, box.endWidget];
|
||||||
|
}
|
||||||
|
|
||||||
|
setChildren(box: CenterBox, children: any[]) {
|
||||||
|
const ch = filter(children);
|
||||||
|
|
||||||
|
box.startWidget = ch[0] || new Gtk.Box();
|
||||||
|
box.centerWidget = ch[1] || new Gtk.Box();
|
||||||
|
box.endWidget = ch[2] || new Gtk.Box();
|
||||||
|
}
|
||||||
|
}
|
3
modules/ags/gtk4/widget/subclasses/index.ts
Normal file
3
modules/ags/gtk4/widget/subclasses/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './box';
|
||||||
|
export * from './button';
|
||||||
|
export * from './centerbox';
|
Loading…
Reference in a new issue