From 41cb376a02d12f85eb1e4893425af15614c2e187 Mon Sep 17 00:00:00 2001 From: Aylur Date: Sun, 9 Jun 2024 22:25:40 +0200 Subject: support deeply nested layout structures js, jsx, lua now allows deeply nested layouts that contains Bindings Variable.derive will be automatically constructed where needed and Bindings in layouts will be bound automatically it breaks compatibility with ags even more, but jsx should be preferred imo anyway --- lua/astal/widget.lua | 211 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 151 insertions(+), 60 deletions(-) (limited to 'lua/astal/widget.lua') 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) -- cgit v1.2.3