Skip to content

Commit

Permalink
rework widget subclassing #121
Browse files Browse the repository at this point in the history
  • Loading branch information
Aylur committed Oct 30, 2023
1 parent 96f1dc8 commit 2af41ee
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 189 deletions.
6 changes: 3 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ export function bulkDisconnect(service: GObject.Object, ids: number[]) {
service.disconnect(id);
}

export function connect(
export function connect<T extends Gtk.Widget>(
obj: GObject.Object,
widget: Gtk.Widget,
callback: (widget: Gtk.Widget, ...args: unknown[]) => void,
widget: T,
callback: (widget: T, ...args: unknown[]) => void,
event?: string,
) {
if (!(obj instanceof Service || obj instanceof App || obj instanceof Variable) && !event)
Expand Down
71 changes: 49 additions & 22 deletions src/widget.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint-disable max-len */
import Gtk from 'gi://Gtk?version=3.0';
import AgsWidget from './widgets/widget.js';
import GObject from 'gi://GObject?version=2.0';
import AgsWidget, { type BaseProps } from './widgets/widget.js';
import AgsBox from './widgets/box.js';
import AgsCenterBox from './widgets/centerbox.js';
import AgsEventBox from './widgets/eventbox.js';
Expand All @@ -17,24 +19,27 @@ import { AgsMenu, AgsMenuItem } from './widgets/menu.js';
import AgsWindow from './widgets/window.js';
import AgsCircularProgress from './widgets/circularprogress.js';

// @ts-expect-error margin override
export const Window = AgsWidget(AgsWindow);
export const Box = AgsWidget(AgsBox);
export const Button = AgsWidget(AgsButton);
export const CenterBox = AgsWidget(AgsCenterBox);
export const CircularProgress = AgsWidget(AgsCircularProgress);
export const Entry = AgsWidget(AgsEntry);
export const EventBox = AgsWidget(AgsEventBox);
export const Icon = AgsWidget(AgsIcon);
export const Label = AgsWidget(AgsLabel);
export const Menu = AgsWidget(AgsMenu);
export const MenuItem = AgsWidget(AgsMenuItem);
export const Overlay = AgsWidget(AgsOverlay);
export const ProgressBar = AgsWidget(AgsProgressBar);
export const Revealer = AgsWidget(AgsRevealer);
export const Scrollable = AgsWidget(AgsScrollable);
export const Slider = AgsWidget(AgsSlider);
export const Stack = AgsWidget(AgsStack);
function createCtor<T extends typeof Gtk.Widget>(Widget: T) {
return (...props: ConstructorParameters<T>) => new Widget(...props) as InstanceType<T>;
}

export const Window = createCtor(AgsWindow);
export const Box = createCtor(AgsBox);
export const Button = createCtor(AgsButton);
export const CenterBox = createCtor(AgsCenterBox);
export const CircularProgress = createCtor(AgsCircularProgress);
export const Entry = createCtor(AgsEntry);
export const EventBox = createCtor(AgsEventBox);
export const Icon = createCtor(AgsIcon);
export const Label = createCtor(AgsLabel);
export const Menu = createCtor(AgsMenu);
export const MenuItem = createCtor(AgsMenuItem);
export const Overlay = createCtor(AgsOverlay);
export const ProgressBar = createCtor(AgsProgressBar);
export const Revealer = createCtor(AgsRevealer);
export const Scrollable = createCtor(AgsScrollable);
export const Slider = createCtor(AgsSlider);
export const Stack = createCtor(AgsStack);

const ctors = new Map();
export function Widget<
Expand All @@ -46,9 +51,9 @@ export function Widget<
if (ctors.has(type))
return ctors.get(type)(props);

const ctor = AgsWidget(type);
ctors.set(type, ctor);
return ctor(props);
const Ctor = AgsWidget(type);
ctors.set(type, Ctor);
return new Ctor(props);
}

// so they are still accessible when importing only Widget
Expand All @@ -70,4 +75,26 @@ Widget.Slider = Slider;
Widget.Stack = Stack;
Widget.Window = Window;

export function subclass<T extends typeof Gtk.Widget, Props>(W: T) {
class Widget extends AgsWidget(W, `Gtk${W.name}`) {
static { GObject.registerClass({ GTypeName: `Ags${W.name}` }, this); }
constructor(props: BaseProps<InstanceType<T> & Widget> & Props) {
super(props as Gtk.Widget.ConstructorProperties);
}
}
return (props: BaseProps<InstanceType<T> & Widget> & Props) => new Widget(props) as InstanceType<T> & Widget;
}

export const Calendar = subclass<typeof Gtk.Calendar, Gtk.Calendar.ConstructorProperties>(Gtk.Calendar);
export const Fixed = subclass<typeof Gtk.Fixed, Gtk.Fixed.ConstructorProperties>(Gtk.Fixed);
export const MenuBar = subclass<typeof Gtk.MenuBar, Gtk.MenuBar.ConstructorProperties>(Gtk.MenuBar);
export const Switch = subclass<typeof Gtk.Switch, Gtk.Switch.ConstructorProperties>(Gtk.Switch);
export const ToggleButton = subclass<typeof Gtk.ToggleButton, Gtk.ToggleButton.ConstructorProperties>(Gtk.ToggleButton);

Widget.Calendar = Calendar;
Widget.Fixed = Fixed;
Widget.MenuBar = MenuBar;
Widget.Switch = Switch;
Widget.ToggleButton = ToggleButton;

export default Widget;
8 changes: 5 additions & 3 deletions src/widgets/box.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import Service from '../service.js';

export interface BoxProps extends Gtk.Box.ConstructorProperties {
export interface BoxProps<T> extends BaseProps<T>, Gtk.Box.ConstructorProperties {
children?: Gtk.Widget[]
vertical?: boolean
}

export default class AgsBox extends Gtk.Box {
export default class AgsBox extends AgsWidget(Gtk.Box) {
static {
GObject.registerClass({
GTypeName: 'AgsBox',
Properties: {
'vertical': Service.pspec('vertical', 'boolean', 'rw'),
'children': Service.pspec('children', 'jsobject', 'rw'),
},
}, this);
}

constructor({ children, ...rest }: BoxProps = {}) {
constructor({ children, ...rest }: BoxProps<AgsBox> = {}) {
super(rest);

if (children)
Expand Down
13 changes: 9 additions & 4 deletions src/widgets/button.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import Gdk from 'gi://Gdk?version=3.0';
import { runCmd } from '../utils.js';
import { type Command } from './widget.js';

export interface ButtonProps extends Gtk.Button.ConstructorProperties {
export interface ButtonProps extends BaseProps<AgsButton>, Gtk.Button.ConstructorProperties {
onClicked?: Command
onPrimaryClick?: Command
onSecondaryClick?: Command
Expand All @@ -18,8 +19,12 @@ export interface ButtonProps extends Gtk.Button.ConstructorProperties {
onScrollDown?: Command
}

export default class AgsButton extends Gtk.Button {
static { GObject.registerClass(this); }
export default class AgsButton extends AgsWidget(Gtk.Button) {
static {
GObject.registerClass({
GTypeName: 'AgsButton',
}, this);
}

onClicked: Command;
onPrimaryClick: Command;
Expand Down Expand Up @@ -101,7 +106,7 @@ export default class AgsButton extends Gtk.Button {
return runCmd(this.onMiddleClickRelease, this, event);
});

this.connect('scroll-event', (box, event) => {
this.connect('scroll-event', (box, event: Gdk.Event) => {
if (event.get_scroll_deltas()[2] < 0)
return runCmd(this.onScrollUp, box, event);
else if (event.get_scroll_deltas()[2] > 0)
Expand Down
6 changes: 3 additions & 3 deletions src/widgets/centerbox.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import AgsBox from './box.js';
import AgsBox, { type BoxProps } from './box.js';

export interface CenterBoxProps extends Gtk.Box.ConstructorProperties {
children?: Gtk.Widget[]
export interface CenterBoxProps extends BoxProps<AgsCenterBox> {
start_widget?: Gtk.Widget
center_widget?: Gtk.Widget
end_widget?: Gtk.Widget
Expand All @@ -12,6 +11,7 @@ export interface CenterBoxProps extends Gtk.Box.ConstructorProperties {
export default class AgsCenterBox extends AgsBox {
static {
GObject.registerClass({
GTypeName: 'AgsCenterBox',
Properties: {
'start-widget': GObject.ParamSpec.object(
'start-widget', 'Start Widget', 'Start Widget',
Expand Down
13 changes: 10 additions & 3 deletions src/widgets/circularprogress.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import Service from '../service.js';

// type from gi-types is wrong
interface Context {
setSourceRGBA: (r: number, g: number, b: number, a: number) => void
arc: (x: number, y: number, r: number, a1: number, a2: number) => void
Expand All @@ -13,16 +13,18 @@ interface Context {
$dispose: () => void
}

export interface CircularProgressProps extends Gtk.Bin {
export interface CircularProgressProps extends
BaseProps<AgsCircularProgress>, Gtk.Bin.ConstructorProperties {
rounded?: boolean
value?: number
inverted?: boolean
start_at?: number
}

export default class AgsCircularProgress extends Gtk.Bin {
export default class AgsCircularProgress extends AgsWidget(Gtk.Bin) {
static {
GObject.registerClass({
GTypeName: 'AgsCircularProgress',
CssName: 'circular-progress',
Properties: {
'start-at': Service.pspec('start-at', 'float', 'rw'),
Expand All @@ -33,6 +35,11 @@ export default class AgsCircularProgress extends Gtk.Bin {
}, this);
}

// its here for typescript to infer the type
constructor(props: CircularProgressProps) {
super(props);
}

// @ts-expect-error
get rounded() { return this._rounded || false; }
set rounded(r: boolean) {
Expand Down
11 changes: 8 additions & 3 deletions src/widgets/entry.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import { runCmd } from '../utils.js';
import { type Command } from './widget.js';

export interface EntryProps extends Gtk.Entry.ConstructorProperties {
export interface EntryProps extends BaseProps<AgsEntry>, Gtk.Entry.ConstructorProperties {
onAccept?: Command
onChange?: Command
}

export default class AgsEntry extends Gtk.Entry {
static { GObject.registerClass(this); }
export default class AgsEntry extends AgsWidget(Gtk.Entry) {
static {
GObject.registerClass({
GTypeName: 'AgsEntry',
}, this);
}

onAccept: Command;
onChange: Command;
Expand Down
17 changes: 11 additions & 6 deletions src/widgets/eventbox.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import Gdk from 'gi://Gdk?version=3.0';
import { runCmd } from '../utils.js';
import { type Command } from './widget.js';

export interface EventBoxProps extends Gtk.EventBox.ConstructorProperties {
export interface EventBoxProps extends BaseProps<AgsEventBox>, Gtk.EventBox.ConstructorProperties {
onPrimaryClick?: Command
onSecondaryClick?: Command
onMiddleClick?: Command
Expand All @@ -17,8 +18,12 @@ export interface EventBoxProps extends Gtk.EventBox.ConstructorProperties {
onScrollDown?: Command
}

export default class AgsEventBox extends Gtk.EventBox {
static { GObject.registerClass(this); }
export default class AgsEventBox extends AgsWidget(Gtk.EventBox) {
static {
GObject.registerClass({
GTypeName: 'AgsEventBox',
}, this);
}

onPrimaryClick: Command;
onSecondaryClick: Command;
Expand Down Expand Up @@ -69,7 +74,7 @@ export default class AgsEventBox extends Gtk.EventBox {
return runCmd(this.onHoverLost, box, event);
});

this.connect('button-press-event', (box, event) => {
this.connect('button-press-event', (box, event: Gdk.Event) => {
box.set_state_flags(Gtk.StateFlags.ACTIVE, false);
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
return runCmd(this.onPrimaryClick, box, event);
Expand All @@ -81,7 +86,7 @@ export default class AgsEventBox extends Gtk.EventBox {
return runCmd(this.onMiddleClick, box, event);
});

this.connect('button-release-event', (box, event) => {
this.connect('button-release-event', (box, event: Gdk.Event) => {
box.unset_state_flags(Gtk.StateFlags.ACTIVE);
if (event.get_button()[1] === Gdk.BUTTON_PRIMARY)
return runCmd(this.onPrimaryClickRelease, box, event);
Expand All @@ -93,7 +98,7 @@ export default class AgsEventBox extends Gtk.EventBox {
return runCmd(this.onMiddleClickRelease, box, event);
});

this.connect('scroll-event', (box, event) => {
this.connect('scroll-event', (box, event: Gdk.Event) => {
if (event.get_scroll_deltas()[2] < 0)
return runCmd(this.onScrollUp, box, event);
else if (event.get_scroll_deltas()[2] > 0)
Expand Down
6 changes: 4 additions & 2 deletions src/widgets/icon.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import GLib from 'gi://GLib';
Expand All @@ -6,16 +7,17 @@ import Gdk from 'gi://Gdk?version=3.0';
import Service from '../service.js';
import cairo from '@girs/cairo-1.0';

interface Props extends Gtk.Image.ConstructorProperties {
interface Props extends BaseProps<AgsIcon>, Gtk.Image.ConstructorProperties {
icon?: string | GdkPixbuf.Pixbuf
size?: number
}

export type IconProps = Props | string | GdkPixbuf.Pixbuf | undefined

export default class AgsIcon extends Gtk.Image {
export default class AgsIcon extends AgsWidget(Gtk.Image) {
static {
GObject.registerClass({
GTypeName: 'AgsIcon',
Properties: {
'icon': Service.pspec('icon', 'jsobject', 'rw'),
'size': Service.pspec('size', 'double', 'rw'),
Expand Down
6 changes: 4 additions & 2 deletions src/widgets/label.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AgsWidget, { type BaseProps } from './widget.js';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=3.0';
import GLib from 'gi://GLib';
Expand All @@ -10,16 +11,17 @@ const truncates = ['none', 'start', 'middle', 'end'] as const;
type Justification = typeof justifications[number];
type Truncate = typeof truncates[number];

interface Props extends Gtk.Label.ConstructorProperties {
interface Props extends BaseProps<AgsLabel>, Gtk.Label.ConstructorProperties {
justification?: Justification
truncate?: Truncate
}

export type LabelProps = Props | string | undefined

export default class AgsLabel extends Gtk.Label {
export default class AgsLabel extends AgsWidget(Gtk.Label) {
static {
GObject.registerClass({
GTypeName: 'AgsLabel',
Properties: {
'justification': Service.pspec('justification', 'string', 'rw'),
'truncate': Service.pspec('truncate', 'string', 'rw'),
Expand Down
Loading

0 comments on commit 2af41ee

Please sign in to comment.