summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gjs/src/astalify.ts100
-rw-r--r--gjs/src/jsx/jsx-runtime.ts51
-rw-r--r--lua/astal/binding.lua4
-rw-r--r--lua/astal/variable.lua8
-rw-r--r--lua/astal/widget.lua211
5 files changed, 232 insertions, 142 deletions
diff --git a/gjs/src/astalify.ts b/gjs/src/astalify.ts
index f986716..ecd52d4 100644
--- a/gjs/src/astalify.ts
+++ b/gjs/src/astalify.ts
@@ -1,12 +1,61 @@
import Binding, { kebabify, snakeify, type Connectable, type Subscribable } from "./binding.js"
import { Astal, Gtk } from "./imports.js"
import { execAsync } from "./process.js"
+import Variable from "./variable.js"
Object.defineProperty(Astal.Box.prototype, "children", {
get() { return this.get_children() },
set(v) { this.set_children(v) },
})
+function setChildren(parent: Gtk.Widget, children: Gtk.Widget[]) {
+ children = children.flat(Infinity).map(ch => ch instanceof Gtk.Widget
+ ? ch
+ : new Gtk.Label({ visible: true, label: String(ch) }))
+
+ // remove
+ if (parent instanceof Gtk.Bin) {
+ const ch = parent.get_child()
+ if (ch)
+ parent.remove(ch)
+ }
+
+ // FIXME: add rest of the edge cases like Stack
+ if (parent instanceof Astal.Box) {
+ parent.set_children(children)
+ }
+
+ else if (parent instanceof Astal.CenterBox) {
+ parent.startWidget = children[0]
+ parent.centerWidget = children[1]
+ parent.endWidget = children[2]
+ }
+
+ else if (parent instanceof Astal.Overlay) {
+ const [child, ...overlays] = children
+ parent.set_child(child)
+ parent.set_overlays(overlays)
+ }
+
+ else if (parent instanceof Gtk.Container) {
+ for (const ch of children)
+ parent.add(ch)
+ }
+}
+
+function mergeBindings(array: any[]) {
+ const getValues = () => array.map(i => i instanceof Binding ? i.get() : i)
+ const bindings = array.filter(i => i instanceof Binding)
+
+ if (bindings.length === 0)
+ return array
+
+ if (bindings.length === 1)
+ return bindings[0].as(getValues)
+
+ return Variable.derive(bindings, getValues)()
+}
+
export type Widget<C extends { new(...args: any): Gtk.Widget }> = InstanceType<C> & {
className: string
css: string
@@ -47,7 +96,7 @@ function hook(
return self
}
-function ctor(self: any, config: any = {}, ...children: Gtk.Widget[]) {
+function ctor(self: any, config: any = {}, children: any[] = []) {
const { setup, ...props } = config
props.visible ??= true
@@ -71,9 +120,6 @@ function ctor(self: any, config: any = {}, ...children: Gtk.Widget[]) {
return acc
}, [])
- const pchildren = props.children
- delete props.children
-
Object.assign(self, props)
Object.assign(self, {
hook(obj: any, sig: any, callback: any) {
@@ -91,23 +137,28 @@ function ctor(self: any, config: any = {}, ...children: Gtk.Widget[]) {
}
}
- if (self instanceof Gtk.Container) {
- if (children) {
- for (const child of children)
- self.add(child)
- }
- if (pchildren && Array.isArray(pchildren)) {
- for (const child of pchildren)
- self.add(child)
- }
- }
-
for (const [prop, bind] of bindings) {
+ if (prop === "child" || prop === "children") {
+ self.connect("destroy", bind.subscribe((v: any) => {
+ setChildren(self, v)
+ }))
+ }
self.connect("destroy", bind.subscribe((v: any) => {
self[`set_${snakeify(prop)}`](v)
}))
}
+ children = mergeBindings(children.flat(Infinity))
+ if (children instanceof Binding) {
+ setChildren(self, children.get())
+ self.connect("destroy", children.subscribe(v => {
+ setChildren(self, v)
+ }))
+ }
+ else {
+ setChildren(self, children)
+ }
+
setup?.(self)
return self
}
@@ -130,29 +181,14 @@ function proxify<
set(v) { Astal.widget_set_cursor(this, v) },
})
- klass.prototype.set_child = function(widget: Gtk.Widget) {
- if (this instanceof Gtk.Bin) {
- const rm = this.get_child()
- if (rm)
- this.remove(rm)
- }
- if (this instanceof Gtk.Container)
- this.add(widget)
- }
-
- Object.defineProperty(klass.prototype, "child", {
- get() { return this.get_child?.() },
- set(v) { this.set_child(v) },
- })
-
const proxy = new Proxy(klass, {
construct(_, [conf, ...children]) {
const self = new klass
- return ctor(self, conf, ...children)
+ return ctor(self, conf, children)
},
apply(_t, _a, [conf, ...children]) {
const self = new klass
- return ctor(self, conf, ...children)
+ return ctor(self, conf, children)
},
})
diff --git a/gjs/src/jsx/jsx-runtime.ts b/gjs/src/jsx/jsx-runtime.ts
index 5e7f23b..e96f7c2 100644
--- a/gjs/src/jsx/jsx-runtime.ts
+++ b/gjs/src/jsx/jsx-runtime.ts
@@ -1,12 +1,5 @@
import { Gtk } from "../imports.js"
import * as Widget from "../widgets.js"
-import Binding from "../binding.js"
-
-function w(e: any) {
- return e instanceof Gtk.Widget || e instanceof Binding
- ? e
- : Widget.Label({ label: String(e) })
-}
export function jsx(
ctor: keyof typeof ctors | typeof Gtk.Widget,
@@ -16,46 +9,16 @@ export function jsx(
if (!Array.isArray(children))
children = [children]
- else
- children = children.flat()
-
- // <box children={Binding} /> and <box>{Binding}</box>
- if (ctor === "box" && children.length === 1 && children[0] instanceof Binding) {
- props.children = children[0]
- }
-
- // TODO: handle array of Binding
- // is there a usecase?
-
- else if (ctor === "centerbox") {
- if (children[0])
- props.startWidget = w(children[0])
- if (children[1])
- props.centerWidget = w(children[1])
- if (children[2])
- props.endWidget = w(children[2])
- }
-
- else if (ctor === "overlay") {
- const [child, ...overlays] = children
- if (child)
- props.child = child
-
- props.overlays = overlays
- }
- else if (children.length === 1) {
- props.child = w(children[0])
- delete props.children
- }
+ if (typeof ctor === "string")
+ return (ctors as any)[ctor](props, children)
- else {
- props.children = children.map(w)
- }
+ if (children.length === 1)
+ props.child = children[0]
+ else if (children.length > 1)
+ props.children = children
- return typeof ctor === "string"
- ? (ctors as any)[ctor](props)
- : new ctor(props)
+ return new ctor(props)
}
const ctors = {
diff --git a/lua/astal/binding.lua b/lua/astal/binding.lua
index 264986b..c9929ea 100644
--- a/lua/astal/binding.lua
+++ b/lua/astal/binding.lua
@@ -2,12 +2,12 @@ local lgi = require("lgi")
local GObject = lgi.require("GObject", "2.0")
---@class Binding
----@field emitter object
+---@field emitter table|Variable
---@field property? string
---@field transformFn function
local Binding = {}
----@param emitter object
+---@param emitter table
---@param property? string
---@return Binding
function Binding.new(emitter, property)
diff --git a/lua/astal/variable.lua b/lua/astal/variable.lua
index baa2d69..75f7d1e 100644
--- a/lua/astal/variable.lua
+++ b/lua/astal/variable.lua
@@ -6,11 +6,11 @@ local Time = require("astal.time")
local Process = require("astal.process")
---@class Variable
----@field private variable object
+---@field private variable table
---@field private err_handler? function
---@field private _value any
----@field private _poll? object
----@field private _watch? object
+---@field private _poll? table
+---@field private _watch? table
---@field private poll_interval number
---@field private poll_exec? string[] | string
---@field private poll_transform? fun(next: any, prev: any): any
@@ -193,7 +193,7 @@ function Variable:watch(exec, transform)
return self
end
----@param object object | table[]
+---@param object table | table[]
---@param sigOrFn string | fun(...): any
---@param callback fun(...): any
---@return Variable
diff --git a/lua/astal/widget.lua b/lua/astal/widget.lua
index a10bd1a..7a88a1c 100644
--- a/lua/astal/widget.lua
+++ b/lua/astal/widget.lua
@@ -3,65 +3,110 @@ local Astal = lgi.require("Astal", "0.1")
local Gtk = lgi.require("Gtk", "3.0")
local GObject = lgi.require("GObject", "2.0")
local Binding = require("astal.binding")
+local Variable = require("astal.variable")
local exec_async = require("astal.process").exec_async
local function filter(tbl, fn)
local copy = {}
for key, value in pairs(tbl) do
if fn(value, key) then
- copy[key] = value
+ if type(key) == "number" then
+ table.insert(copy, value)
+ else
+ copy[key] = value
+ end
+ end
+ end
+ return copy
+end
+
+local function map(tbl, fn)
+ local copy = {}
+ for key, value in pairs(tbl) do
+ copy[key] = fn(value)
+ end
+ return copy
+end
+
+local flatten
+flatten = function(tbl)
+ local copy = {}
+ for _, value in pairs(tbl) do
+ if type(value) == "table" and getmetatable(value) == nil then
+ for _, inner in pairs(flatten(value)) do
+ table.insert(copy, inner)
+ end
+ else
+ table.insert(copy, value)
end
end
return copy
end
-local function set_child(parent, child)
- if parent.get_child ~= nil then
+local function set_children(parent, children)
+ children = map(flatten(children), function(item)
+ if Gtk.Widget:is_type_of(item) then
+ return item
+ end
+ return Gtk.Label({
+ visible = true,
+ label = tostring(item),
+ })
+ end)
+
+ -- remove
+ if Gtk.Bin:is_type_of(parent) then
local rm = parent:get_child()
if rm ~= nil then
parent:remove(rm)
end
end
- if parent.add ~= nil then
- parent:add(child)
+
+ -- FIXME: add rest of the edge cases like Stack
+ if Astal.Box:is_type_of(parent) then
+ parent:set_children(children)
+ elseif Astal.CenterBox:is_type_of(parent) then
+ parent.start_widget = children[1]
+ parent.center_widget = children[2]
+ parent.end_widget = children[3]
+ elseif Astal.Overlay:is_type_of(parent) then
+ parent:set_child(children[1])
+ children[1] = nil
+ parent:set_overlays(children)
+ elseif Gtk.Container:is_type_of(parent) then
+ for _, child in pairs(children) do
+ if Gtk.Widget:is_type_of(child) then
+ parent:add(child)
+ end
+ end
end
end
-Gtk.Widget._attribute.css = {
- get = Astal.widget_get_css,
- set = Astal.widget_set_css,
-}
-
-Gtk.Widget._attribute.class_name = {
- get = function(self)
- local result = ""
- local strings = Astal.widget_set_class_names(self)
- for i, str in ipairs(strings) do
- result = result .. str
- if i < #strings then
- result = result .. " "
+local function merge_bindings(array)
+ local function get_values()
+ return map(array, function(v)
+ if getmetatable(v) == Binding then
+ return v:get()
+ else
+ return v
end
- end
- return result
- end,
- set = function(self, class_name)
- local names = {}
- for word in class_name:gmatch("%S+") do
- table.insert(names, word)
- end
- Astal.widget_set_class_names(self, names)
- end,
-}
+ end)
+ end
-Gtk.Widget._attribute.cursor = {
- get = Astal.widget_get_cursor,
- set = Astal.widget_set_cursor,
-}
+ local bindings = filter(array, function(v)
+ return getmetatable(v) == Binding
+ end)
-Astal.Box._attribute.children = {
- get = Astal.Box.get_children,
- set = Astal.Box.set_children,
-}
+ if #bindings == 0 then
+ return array
+ end
+
+ if #bindings == 1 then
+ return bindings[1]:as(get_values)
+ end
+
+ return Variable.derive(bindings, get_values)()
+end
local function astalify(ctor)
function ctor:hook(object, signalOrCallback, callback)
@@ -88,24 +133,22 @@ local function astalify(ctor)
local bindings = {}
local setup = tbl.setup
- local visible
- if type(tbl.visible) == "boolean" then
- visible = tbl.visible
- else
- visible = true
+ -- collect children
+ local children = merge_bindings(flatten(filter(tbl, function(_, key)
+ return type(key) == "number"
+ end)))
+
+ -- default visible to true
+ if type(tbl.visible) ~= "boolean" then
+ tbl.visible = true
end
+ -- filter props
local props = filter(tbl, function(_, key)
- return key ~= "visible" and key ~= "setup"
+ return type(key) == "string" and key ~= "setup"
end)
- for prop, value in pairs(props) do
- if getmetatable(value) == Binding then
- bindings[prop] = value
- props[prop] = value:get()
- end
- end
-
+ -- handle on_ handlers that are strings
for prop, value in pairs(props) do
if string.sub(prop, 0, 2) == "on" and type(value) ~= "function" then
props[prop] = function()
@@ -114,24 +157,36 @@ local function astalify(ctor)
end
end
+ -- handle bindings
+ for prop, value in pairs(props) do
+ if getmetatable(value) == Binding then
+ bindings[prop] = value
+ props[prop] = value:get()
+ end
+ end
+
+ -- construct, attach bindings, add children
local widget = ctor(props)
for prop, binding in pairs(bindings) do
- if prop == "child" then
- widget.on_destroy = binding:subscribe(function(v)
- set_child(widget, v)
- end)
- else
- widget.on_destroy = binding:subscribe(function(v)
- widget[prop] = v
- end)
- end
+ widget.on_destroy = binding:subscribe(function(v)
+ widget[prop] = v
+ end)
+ end
+
+ if getmetatable(children) == Binding then
+ set_children(widget, children:get())
+ widget.on_destroy = children:subscribe(function(v)
+ set_children(widget, v)
+ end)
+ else
+ set_children(widget, children)
end
- widget.visible = visible
if type(setup) == "function" then
setup(widget)
end
+
return widget
end
end
@@ -160,6 +215,42 @@ local Widget = {
Window = astalify(Astal.Window),
}
+Gtk.Widget._attribute.css = {
+ get = Astal.widget_get_css,
+ set = Astal.widget_set_css,
+}
+
+Gtk.Widget._attribute.class_name = {
+ get = function(self)
+ local result = ""
+ local strings = Astal.widget_set_class_names(self)
+ for i, str in ipairs(strings) do
+ result = result .. str
+ if i < #strings then
+ result = result .. " "
+ end
+ end
+ return result
+ end,
+ set = function(self, class_name)
+ local names = {}
+ for word in class_name:gmatch("%S+") do
+ table.insert(names, word)
+ end
+ Astal.widget_set_class_names(self, names)
+ end,
+}
+
+Gtk.Widget._attribute.cursor = {
+ get = Astal.widget_get_cursor,
+ set = Astal.widget_set_cursor,
+}
+
+Astal.Box._attribute.children = {
+ get = Astal.Box.get_children,
+ set = Astal.Box.set_children,
+}
+
return setmetatable(Widget, {
__call = function(_, ctor)
return astalify(ctor)