feat: add support for circular-progress widget

This commit is contained in:
Byson94
2025-10-26 14:05:54 +05:30
parent e0ae5c97d4
commit 187a367977
5 changed files with 269 additions and 324 deletions

View File

@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- `focusable` property to all widget.
- `widget_name` property to all widget.
- `lifetime` flag for update command.
- `circular-progress` widget back.
### Fixed

View File

@@ -350,7 +350,12 @@ impl<B: DisplayBackend> App<B> {
let output = format!("{:#?}", &self.pl_handler_store.read().unwrap());
sender.send_success(output)?
}
DaemonCommand::TriggerUpdateUI { inject_vars, should_preserve_state, lifetime, sender } => {
DaemonCommand::TriggerUpdateUI {
inject_vars,
should_preserve_state,
lifetime,
sender,
} => {
match self.trigger_ui_update_with(inject_vars, should_preserve_state, lifetime) {
Ok(_) => sender.send_success(String::new())?,
Err(e) => sender.send_failure(e.to_string())?,
@@ -406,9 +411,7 @@ impl<B: DisplayBackend> App<B> {
// let scope_index = ewwii_window.scope_index;
ewwii_window.close();
println!("VAR: {:#?}", self.clear_pl_onclose);
if let Some(var_name) = self.clear_pl_onclose.remove(instance_id) {
println!("NAME: {}", var_name);
self.pl_handler_store.write().unwrap().remove(&var_name);
}
@@ -736,7 +739,7 @@ impl<B: DisplayBackend> App<B> {
&mut self,
inject_vars: Option<HashMap<String, String>>,
should_preserve_state: bool,
lifetime: Option<String>
lifetime: Option<String>,
) -> Result<()> {
let compiled_ast = self.ewwii_config.get_owned_compiled_ast();
let config_path = self.paths.get_rhai_path();
@@ -856,36 +859,34 @@ impl<B: DisplayBackend> App<B> {
let cp = self.config_parser.clone();
let wgs = self.widget_reg_store.clone();
let handle_request = move |req: PluginRequest| {
match req {
PluginRequest::RhaiEngineAct(func) => {
cp.borrow_mut().action_with_engine(func);
let handle_request = move |req: PluginRequest| match req {
PluginRequest::RhaiEngineAct(func) => {
cp.borrow_mut().action_with_engine(func);
}
PluginRequest::RegisterFunc((name, func)) => {
if let Err(e) = shared_utils::slib_store::register_functions(name, func) {
log::error!("Error registering function: {}", e);
}
PluginRequest::RegisterFunc((name, func)) => {
if let Err(e) = shared_utils::slib_store::register_functions(name, func) {
log::error!("Error registering function: {}", e);
}
}
PluginRequest::ListWidgetIds(res_tx) => {
let wgs_guard = wgs.lock().unwrap();
if let Some(wgs_brw) = wgs_guard.as_ref() {
let output: Vec<u64> = wgs_brw.widgets.keys().cloned().collect();
let _ = res_tx.send(output);
}
PluginRequest::ListWidgetIds(res_tx) => {
let wgs_guard = wgs.lock().unwrap();
if let Some(wgs_brw) = wgs_guard.as_ref() {
let output: Vec<u64> = wgs_brw.widgets.keys().cloned().collect();
let _ = res_tx.send(output);
}
}
PluginRequest::WidgetRegistryAct(func) => {
let mut wgs_guard = wgs.lock().unwrap();
if let Some(ref mut registry) = *wgs_guard {
let repr_map: HashMap<u64, &mut gtk4::Widget> = registry
.widgets
.iter_mut()
.map(|(id, entry)| (*id, &mut entry.widget))
.collect();
}
PluginRequest::WidgetRegistryAct(func) => {
let mut wgs_guard = wgs.lock().unwrap();
if let Some(ref mut registry) = *wgs_guard {
let repr_map: HashMap<u64, &mut gtk4::Widget> = registry
.widgets
.iter_mut()
.map(|(id, entry)| (*id, &mut entry.widget))
.collect();
func(&mut ewwii_plugin_api::widget_backend::WidgetRegistryRepr {
widgets: repr_map,
});
}
func(&mut ewwii_plugin_api::widget_backend::WidgetRegistryRepr {
widgets: repr_map,
});
}
}
};

View File

@@ -50,9 +50,9 @@ fn build_gtk_widget_from_node(
WidgetNode::ToolTip { props, children } => {
build_tooltip(props, children, widget_reg)?.upcast()
}
// WidgetNode::CircularProgress { props } => {
// build_circular_progress_bar(props, widget_reg)?.upcast()
// }
WidgetNode::CircularProgress { props } => {
build_circular_progress_bar(props, widget_reg)?.upcast()
}
// WidgetNode::Graph { props } => build_graph(props, widget_reg)?.upcast(),
// WidgetNode::Transform { props } => build_transform(props, widget_reg)?.upcast(),
WidgetNode::Slider { props } => build_gtk_scale(props, widget_reg)?.upcast(),

View File

@@ -1,273 +1,199 @@
// use anyhow::{anyhow, Result};
// use gtk4::glib::{self, object_subclass, prelude::*, wrapper, Properties};
// use gtk4::{cairo, gdk, prelude::*, subclass::prelude::*};
// use std::cell::RefCell;
use glib::Object;
use gtk4::glib;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use gtk4::{cairo, gdk, graphene};
use std::cell::Cell;
// use crate::error_handling_ctx;
mod imp {
use super::*;
// wrapper! {
// pub struct CircProg(ObjectSubclass<CircProgPriv>)
// @extends gtk4::Bin, gtk4::Container, gtk4::Widget;
// }
pub struct CircProg {
pub value: Cell<f64>,
pub start_at: Cell<f64>,
pub thickness: Cell<f64>,
pub clockwise: Cell<bool>,
pub fg_color: Cell<gdk::RGBA>,
pub bg_color: Cell<gdk::RGBA>,
}
// #[derive(Properties)]
// #[properties(wrapper_type = CircProg)]
// pub struct CircProgPriv {
// #[property(
// get,
// set,
// nick = "Starting at",
// blurb = "Starting at",
// minimum = 0f64,
// maximum = 100f64,
// default = 0f64
// )]
// start_at: RefCell<f64>,
impl Default for CircProg {
fn default() -> Self {
Self {
value: Cell::new(0.0),
start_at: Cell::new(0.0),
thickness: Cell::new(8.0),
clockwise: Cell::new(true),
fg_color: Cell::new(gdk::RGBA::new(1.0, 0.0, 0.0, 1.0)),
bg_color: Cell::new(gdk::RGBA::new(0.0, 0.0, 0.0, 0.1)),
}
}
}
// #[property(
// get,
// set,
// nick = "Value",
// blurb = "The value",
// minimum = 0f64,
// maximum = 100f64,
// default = 0f64
// )]
// value: RefCell<f64>,
#[glib::object_subclass]
impl ObjectSubclass for CircProg {
const NAME: &'static str = "CircProg";
type Type = super::CircProg;
type ParentType = gtk4::Widget;
}
// #[property(
// get,
// set,
// nick = "Thickness",
// blurb = "Thickness",
// minimum = 0f64,
// maximum = 100f64,
// default = 1f64
// )]
// thickness: RefCell<f64>,
impl ObjectImpl for CircProg {
fn constructed(&self) {
self.parent_constructed();
// #[property(get, set, nick = "Clockwise", blurb = "Clockwise", default = true)]
// clockwise: RefCell<bool>,
let obj = self.obj();
obj.add_css_class("circular-progress");
}
// content: RefCell<Option<gtk4::Widget>>,
// }
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecDouble::builder("value")
.minimum(0.0)
.maximum(100.0)
.default_value(0.0)
.build(),
glib::ParamSpecDouble::builder("start-at")
.minimum(0.0)
.maximum(100.0)
.default_value(0.0)
.build(),
glib::ParamSpecDouble::builder("thickness")
.minimum(1.0)
.maximum(50.0)
.default_value(8.0)
.build(),
glib::ParamSpecBoolean::builder("clockwise").default_value(true).build(),
glib::ParamSpecBoxed::builder::<gdk::RGBA>("fg-color").build(),
glib::ParamSpecBoxed::builder::<gdk::RGBA>("bg-color").build(),
]
});
PROPERTIES.as_ref()
}
// // This should match the default values from the ParamSpecs
// impl Default for CircProgPriv {
// fn default() -> Self {
// CircProgPriv {
// start_at: RefCell::new(0.0),
// value: RefCell::new(0.0),
// thickness: RefCell::new(1.0),
// clockwise: RefCell::new(true),
// content: RefCell::new(None),
// }
// }
// }
fn set_property(&self, _: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"value" => self.value.set(value.get().unwrap()),
"start-at" => self.start_at.set(value.get().unwrap()),
"thickness" => self.thickness.set(value.get().unwrap()),
"clockwise" => self.clockwise.set(value.get().unwrap()),
"fg-color" => self.fg_color.set(value.get().unwrap()),
"bg-color" => self.bg_color.set(value.get().unwrap()),
x => panic!("Tried to set inexistant property of CircProg: {}", x,),
}
self.obj().queue_draw();
}
// impl ObjectImpl for CircProgPriv {
// fn properties() -> &'static [glib::ParamSpec] {
// Self::derived_properties()
// }
fn property(&self, _: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"value" => self.value.get().to_value(),
"start-at" => self.start_at.get().to_value(),
"thickness" => self.thickness.get().to_value(),
"clockwise" => self.clockwise.get().to_value(),
"fg-color" => self.fg_color.get().to_value(),
"bg-color" => self.bg_color.get().to_value(),
x => panic!("Tried to get inexistant property of CircProg: {}", x,),
}
}
}
// fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
// match pspec.name() {
// "value" => {
// self.value.replace(value.get().unwrap());
// self.obj().queue_draw(); // Queue a draw call with the updated value
// }
// "thickness" => {
// self.thickness.replace(value.get().unwrap());
// }
// "start-at" => {
// self.start_at.replace(value.get().unwrap());
// }
// "clockwise" => {
// self.clockwise.replace(value.get().unwrap());
// }
// x => panic!("Tried to set inexistant property of CircProg: {}", x,),
// }
// }
impl WidgetImpl for CircProg {
fn measure(&self, _orientation: gtk4::Orientation, _for_size: i32) -> (i32, i32, i32, i32) {
let min_size = 32;
let natural_size = 64;
(min_size, natural_size, -1, -1)
}
// fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
// self.derived_property(id, pspec)
// }
// }
fn snapshot(&self, snapshot: &gtk4::Snapshot) {
let value = self.value.get();
let start_at = self.start_at.get();
let thickness = self.thickness.get();
let clockwise = self.clockwise.get();
let fg_color = self.fg_color.get();
let bg_color = self.bg_color.get();
// #[object_subclass]
// impl ObjectSubclass for CircProgPriv {
// type ParentType = gtk4::Bin;
// type Type = CircProg;
let margin_start = self.obj().margin_start() as f64;
let margin_end = self.obj().margin_end() as f64;
let margin_top = self.obj().margin_top() as f64;
let margin_bottom = self.obj().margin_bottom() as f64;
// Padding is not supported yet
// const NAME: &'static str = "CircProg";
let (start_angle, end_angle) = if clockwise {
(0.0, perc_to_rad(value))
} else {
(perc_to_rad(100.0 - value), 2f64 * std::f64::consts::PI)
};
// fn class_init(klass: &mut Self::Class) {
// klass.set_css_name("circular-progress");
// }
// }
let total_width = self.obj().allocated_width() as f64;
let total_height = self.obj().allocated_height() as f64;
let center = (total_width / 2.0, total_height / 2.0);
// impl Default for CircProg {
// fn default() -> Self {
// Self::new()
// }
// }
let circle_width = total_width - margin_start - margin_end;
let circle_height = total_height - margin_top - margin_bottom;
let outer_ring = f64::min(circle_width, circle_height) / 2.0;
let inner_ring = (f64::min(circle_width, circle_height) / 2.0) - thickness;
// impl CircProg {
// pub fn new() -> Self {
// glib::Object::new::<Self>()
// }
// }
// Snapshot Cairo node
let cr = snapshot.append_cairo(&graphene::Rect::new(
0.0_f32,
0.0_f32,
total_width as f32,
total_height as f32,
));
// impl ContainerImpl for CircProgPriv {
// fn add(&self, widget: &gtk4::Widget) {
// if let Some(content) = &*self.content.borrow() {
// // TODO: Handle this error when populating children widgets instead
// error_handling_ctx::print_error(anyhow!(
// "Error, trying to add multiple children to a circular-progress widget"
// ));
// self.parent_remove(content);
// }
// self.parent_add(widget);
// self.content.replace(Some(widget.clone()));
// }
// }
cr.save().unwrap();
// fn calc_widget_lowest_preferred_dimension(widget: &gtk4::Widget) -> (i32, i32) {
// let preferred_width = widget.preferred_width();
// let preferred_height = widget.preferred_height();
// let min_lowest = i32::min(preferred_width.0, preferred_height.0);
// let natural_lowest = i32::min(preferred_width.1, preferred_height.1);
// (min_lowest, natural_lowest)
// }
// Centering
cr.translate(center.0, center.1);
cr.rotate(perc_to_rad(start_at));
cr.translate(-center.0, -center.1);
// impl BinImpl for CircProgPriv {}
// Background Ring
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, outer_ring, 0.0, perc_to_rad(100.0));
cr.set_source_rgba(
bg_color.red().into(),
bg_color.green().into(),
bg_color.blue().into(),
bg_color.alpha().into(),
);
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, inner_ring, 0.0, perc_to_rad(100.0));
cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
cr.fill().unwrap();
// impl WidgetImpl for CircProgPriv {
// // We overwrite preferred_* so that overflowing content from the children gets cropped
// // We return min(child_width, child_height)
// fn preferred_width(&self) -> (i32, i32) {
// let styles = self.obj().style_context();
// let margin = styles.margin(gtk4::StateFlags::NORMAL);
// Foreground Ring
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, outer_ring, start_angle, end_angle);
cr.set_source_rgba(
fg_color.red().into(),
fg_color.green().into(),
fg_color.blue().into(),
fg_color.alpha().into(),
);
cr.move_to(center.0, center.1);
cr.arc(center.0, center.1, inner_ring, start_angle, end_angle);
cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
cr.fill().unwrap();
// if let Some(child) = &*self.content.borrow() {
// let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);
// (
// min_child + margin.right as i32 + margin.left as i32,
// natural_child + margin.right as i32 + margin.left as i32,
// )
// } else {
// let empty_width =
// (2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;
// (empty_width, empty_width)
// }
// }
cr.restore().unwrap();
}
}
}
// fn preferred_width_for_height(&self, _height: i32) -> (i32, i32) {
// self.preferred_width()
// }
glib::wrapper! {
pub struct CircProg(ObjectSubclass<imp::CircProg>)
@extends gtk4::Widget,
@implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget;
}
// fn preferred_height(&self) -> (i32, i32) {
// let styles = self.obj().style_context();
// let margin = styles.margin(gtk4::StateFlags::NORMAL);
impl CircProg {
pub fn new() -> Self {
Object::builder().build()
}
}
// if let Some(child) = &*self.content.borrow() {
// let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);
// (
// min_child + margin.bottom as i32 + margin.top as i32,
// natural_child + margin.bottom as i32 + margin.top as i32,
// )
// } else {
// let empty_height =
// (2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;
// (empty_height, empty_height)
// }
// }
// fn preferred_height_for_width(&self, _width: i32) -> (i32, i32) {
// self.preferred_height()
// }
// fn draw(&self, cr: &cairo::Context) -> glib::Propagation {
// let res: Result<()> = (|| {
// let value = *self.value.borrow();
// let start_at = *self.start_at.borrow();
// let thickness = *self.thickness.borrow();
// let clockwise = *self.clockwise.borrow();
// let styles = self.obj().style_context();
// let margin = styles.margin(gtk4::StateFlags::NORMAL);
// // Padding is not supported yet
// let fg_color: gdk::RGBA = styles.color(gtk4::StateFlags::NORMAL);
// let bg_color: gdk::RGBA = styles
// .style_property_for_state("background-color", gtk4::StateFlags::NORMAL)
// .get()?;
// let (start_angle, end_angle) = if clockwise {
// (0.0, perc_to_rad(value))
// } else {
// (perc_to_rad(100.0 - value), 2f64 * std::f64::consts::PI)
// };
// let total_width = self.obj().allocated_width() as f64;
// let total_height = self.obj().allocated_height() as f64;
// let center = (total_width / 2.0, total_height / 2.0);
// let circle_width = total_width - margin.left as f64 - margin.right as f64;
// let circle_height = total_height - margin.top as f64 - margin.bottom as f64;
// let outer_ring = f64::min(circle_width, circle_height) / 2.0;
// let inner_ring = (f64::min(circle_width, circle_height) / 2.0) - thickness;
// cr.save()?;
// // Centering
// cr.translate(center.0, center.1);
// cr.rotate(perc_to_rad(start_at));
// cr.translate(-center.0, -center.1);
// // Background Ring
// cr.move_to(center.0, center.1);
// cr.arc(center.0, center.1, outer_ring, 0.0, perc_to_rad(100.0));
// cr.set_source_rgba(bg_color.red(), bg_color.green(), bg_color.blue(), bg_color.alpha());
// cr.move_to(center.0, center.1);
// cr.arc(center.0, center.1, inner_ring, 0.0, perc_to_rad(100.0));
// cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
// cr.fill()?;
// // Foreground Ring
// cr.move_to(center.0, center.1);
// cr.arc(center.0, center.1, outer_ring, start_angle, end_angle);
// cr.set_source_rgba(fg_color.red(), fg_color.green(), fg_color.blue(), fg_color.alpha());
// cr.move_to(center.0, center.1);
// cr.arc(center.0, center.1, inner_ring, start_angle, end_angle);
// cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other
// cr.fill()?;
// cr.restore()?;
// // Draw the children widget, clipping it to the inside
// if let Some(child) = &*self.content.borrow() {
// cr.save()?;
// // Center circular clip
// cr.arc(center.0, center.1, inner_ring + 1.0, 0.0, perc_to_rad(100.0));
// cr.set_source_rgba(bg_color.red(), 0.0, 0.0, bg_color.alpha());
// cr.clip();
// // Children widget
// self.obj().propagate_draw(child, cr);
// cr.reset_clip();
// cr.restore()?;
// }
// Ok(())
// })();
// if let Err(error) = res {
// error_handling_ctx::print_error(error)
// };
// glib::Propagation::Proceed
// }
// }
// fn perc_to_rad(n: f64) -> f64 {
// (n / 100f64) * 2f64 * std::f64::consts::PI
// }
fn perc_to_rad(n: f64) -> f64 {
(n / 100f64) * 2f64 * std::f64::consts::PI
}

View File

@@ -26,6 +26,7 @@ use std::{
// custom widgets
// use crate::widgets::{circular_progressbar::CircProg, transform::Transform};
use crate::widgets::circular_progressbar::CircProg;
/// Connect a gtk signal handler inside of this macro to ensure that when the same code gets run multiple times,
/// the previously connected singal handler first gets disconnected.
@@ -798,7 +799,7 @@ pub(crate) fn build_gtk_flowbox(
if let Ok(default_select) = get_i32_prop(&props, "default_select", None) {
if let Some(child) = gtk_widget.child_at_index(default_select) {
gtk_widget.select_child(&child);
gtk_widget.select_child(&child);
child.grab_focus();
} else {
log::error!("Failed to get child at index {} from FlowBox", default_select);
@@ -988,54 +989,66 @@ pub(super) fn build_gtk_stack(
// Ok(widget)
// }
// pub(super) fn build_circular_progress_bar(
// props: &Map,
// widget_registry: &mut WidgetRegistry,
// ) -> Result<CircProg> {
// let widget = CircProg::new();
pub(super) fn build_circular_progress_bar(
props: &Map,
widget_registry: &mut WidgetRegistry,
) -> Result<CircProg> {
let widget = CircProg::new();
// let apply_props = |props: &Map, widget: &CircProg| -> Result<()> {
// if let Ok(value) = get_f64_prop(&props, "value", None) {
// widget.set_property("value", value.clamp(0.0, 100.0));
// }
let apply_props = |props: &Map, widget: &CircProg| -> Result<()> {
if let Ok(value) = get_f64_prop(&props, "value", None) {
widget.set_property("value", value.clamp(0.0, 100.0));
}
// if let Ok(start_at) = get_f64_prop(&props, "start_at", None) {
// widget.set_property("start-at", start_at.clamp(0.0, 100.0));
// }
if let Ok(start_at) = get_f64_prop(&props, "start_at", None) {
widget.set_property("start-at", start_at.clamp(0.0, 100.0));
}
// if let Ok(thickness) = get_f64_prop(&props, "thickness", None) {
// widget.set_property("thickness", thickness);
// }
if let Ok(thickness) = get_f64_prop(&props, "thickness", None) {
widget.set_property("thickness", thickness);
}
// if let Ok(clockwise) = get_f64_prop(&props, "clockwise", None) {
// widget.set_property("clockwise", clockwise);
// }
if let Ok(clockwise) = get_f64_prop(&props, "clockwise", None) {
widget.set_property("clockwise", clockwise);
}
// Ok(())
// };
if let Ok(fg_color_str) = get_string_prop(&props, "fg_color", None) {
if let Ok(rgba) = gdk::RGBA::parse(fg_color_str) {
widget.set_property("fg-color", rgba);
}
}
// apply_props(&props, &widget)?;
if let Ok(bg_color_str) = get_string_prop(&props, "bg_color", None) {
if let Ok(rgba) = gdk::RGBA::parse(bg_color_str) {
widget.set_property("bg-color", rgba);
}
}
// let widget_clone = widget.clone();
// let update_fn: UpdateFn = Box::new(move |props: &Map| {
// let _ = apply_props(props, &widget_clone);
Ok(())
};
// // now re-apply generic widget attrs
// if let Err(err) =
// resolve_rhai_widget_attrs(&widget_clone.clone().upcast::<gtk4::Widget>(), &props)
// {
// eprintln!("Failed to update widget attrs: {:?}", err);
// }
// });
apply_props(&props, &widget)?;
// let id = hash_props_and_type(&props, "CircularProgressBar");
let widget_clone = widget.clone();
let update_fn: UpdateFn = Box::new(move |props: &Map| {
let _ = apply_props(props, &widget_clone);
// widget_registry.widgets.insert(id, WidgetEntry { update_fn, widget: widget.clone().upcast() });
// now re-apply generic widget attrs
if let Err(err) =
resolve_rhai_widget_attrs(&widget_clone.clone().upcast::<gtk4::Widget>(), &props)
{
eprintln!("Failed to update widget attrs: {:?}", err);
}
});
// resolve_rhai_widget_attrs(&widget.clone().upcast::<gtk4::Widget>(), &props)?;
let id = hash_props_and_type(&props, "CircularProgressBar");
// Ok(widget)
// }
widget_registry.widgets.insert(id, WidgetEntry { update_fn, widget: widget.clone().upcast() });
resolve_rhai_widget_attrs(&widget.clone().upcast::<gtk4::Widget>(), &props)?;
Ok(widget)
}
// pub(super) fn build_graph(
// props: &Map,
@@ -2357,12 +2370,16 @@ pub(super) fn resolve_rhai_widget_attrs(gtk_widget: &gtk4::Widget, props: &Map)
(Some(w), Some(h)) => gtk_widget.set_size_request(w, h),
(Some(w), None) => {
let h = gtk_widget.allocated_height();
if h > 0 { gtk_widget.set_size_request(w, h); }
},
if h > 0 {
gtk_widget.set_size_request(w, h);
}
}
(None, Some(h)) => {
let w = gtk_widget.allocated_width();
if w > 0 { gtk_widget.set_size_request(w, h); }
},
if w > 0 {
gtk_widget.set_size_request(w, h);
}
}
(None, None) => {}
}