diff --git a/cmake/rust/union_Cargo.toml.in b/cmake/rust/union_Cargo.toml.in index 369ea90d91..bf2302cdb2 100644 --- a/cmake/rust/union_Cargo.toml.in +++ b/cmake/rust/union_Cargo.toml.in @@ -4,7 +4,10 @@ name = "${CARGO_CRATE_NAME}" version = "${CARGO_CRATE_VERSION}" edition = "2021" - + +[features] +tracy = [] + [dependencies] ${CARGO_CRATE_DEPS} diff --git a/docs/samples/shards/UI/Table/1.shs b/docs/samples/shards/UI/Table/1.shs index b9e364e39b..7c22912eec 100644 --- a/docs/samples/shards/UI/Table/1.shs +++ b/docs/samples/shards/UI/Table/1.shs @@ -7,31 +7,33 @@ GFX.MainWindow( UI( UI.CentralPanel( Contents: { - [ - {Name: "Doe" Surname: "John"} + [{Name: "Doe" Surname: "John"} {Name: "Dough" Surname: "Jane"} - {Name: "Smith" Surname: "Dick"}] | UI.Table( + {Name: "Smith" Surname: "Dick"}] = seq + Count(seq) | UI.Table( Resizable: true Striped: true - RowIndex: index Columns: [ - {Initial: 20.0} { - Header: "Surname" - Initial: 100.0 - AtLeast: 60.0 - AtMost: 160.0 + UI.TableHeader({ + "Index" | UI.Label + } WidthType: "exact" Width: 20.0) + ToString | UI.Label } { - Header: "Name" - Initial: 80.0 - AtLeast: 60.0 - AtMost: 160.0 + UI.TableHeader({ + "Surname" | UI.Label + } WidthType: "initial" Width: 100.0 MinWidth: 60.0 MaxWidth: 160.0) + = i + seq | Take(i) | Take("Surname") | UI.Label + } + { + UI.TableHeader({ + "Name" | UI.Label + } WidthType: "initial" Width: 80.0 MinWidth: 60.0 MaxWidth: 160.0) + = i + seq | Take(i) | Take("Name") | UI.Label }] - Builder: [ - {index | ToString | UI.Label} - {Take("Surname") | UI.Label} - {Take("Name") | UI.Label}] ) } ) diff --git a/docs/samples/shards/UI/Table/2.shs b/docs/samples/shards/UI/Table/2.shs index cdf49b2623..4276115462 100644 --- a/docs/samples/shards/UI/Table/2.shs +++ b/docs/samples/shards/UI/Table/2.shs @@ -7,19 +7,33 @@ GFX.MainWindow( UI( UI.CentralPanel( Contents: { - [ - @i2(0) @i2(0 1) @i2(1) @i2(1 0)] | UI.Table( + [@i2(0) @i2(0 1) @i2(1) @i2(1 0)] = data + Count(data) | UI.Table( Columns: [ - {Header: "A"} - {Header: "B"} - {Header: "A xor B"}] - Builder: [ - {Take(0) | ToString | UI.Label} - {Take(1) | ToString | UI.Label} { - {Take(0) >= a} - {Take(1) >= b} - a | Math.Xor(b) | ToString | UI.Label + UI.TableHeader({ + "A" | UI.Label + } WidthType: "auto") + = i + data | Take(i) | Take(0) | ToString | UI.Label + } + { + UI.TableHeader({ + "B" | UI.Label + } WidthType: "auto") + = i + data | Take(i) | Take(1) | ToString | UI.Label + } + { + UI.TableHeader({ + "A xor B" | UI.Label + } WidthType: "remainder") + = i + { + data | Take(i) | Take(0) >= a + data | Take(i) | Take(1) >= b + a | Math.Xor(b) | ToString | UI.Label + } }] ) } diff --git a/docs/samples/shards/UI/Table/3.shs b/docs/samples/shards/UI/Table/3.shs index ab66881144..57bb16a283 100644 --- a/docs/samples/shards/UI/Table/3.shs +++ b/docs/samples/shards/UI/Table/3.shs @@ -7,35 +7,35 @@ GFX.MainWindow( UI( UI.CentralPanel( Contents: { - [ - {Name: "Doe" Surname: "John"} + [{Name: "Doe" Surname: "John"} {Name: "Dough" Surname: "Jane"} - {Name: "Smith" Surname: "Dick"}] | UI.Table( + {Name: "Smith" Surname: "Dick"}] = seq + Count(seq) | UI.Table( Resizable: true Striped: true - RowIndex: index Columns: [ - {Initial: 20.0} { - Header: "Surname" - Initial: 100.0 - AtLeast: 60.0 - AtMost: 160.0 + UI.TableHeader({ + "Index" | UI.Label + } WidthType: "exact" Width: 20.0) + ToString | UI.Label } { - Header: { + UI.TableHeader({ + "Surname" | UI.Label + } WidthType: "initial" Width: 100.0 MinWidth: 60.0 MaxWidth: 160.0) + = i + seq | Take(i) | Take("Surname") | UI.Label + } + { + UI.TableHeader({ "Name" | UI.Label(Style: {text_style: "Heading"}) UI.Button("Up" Msg("Clicked Up") Style: {text_style: "Small"}) UI.Button("Down" Msg("Clicked Down") Style: {text_style: "Small"}) - } - Initial: 120.0 - AtLeast: 100.0 - AtMost: 160.0 + } WidthType: "initial" Width: 120.0 MinWidth: 100.0 MaxWidth: 160.0) + = i + seq | Take(i) | Take("Name") | UI.Label }] - Builder: [ - {index | ToString | UI.Label} - {Take("Surname") | UI.Label} - {Take("Name") | UI.Label}] ) } ) diff --git a/shards/egui/src/bindings/mod.rs b/shards/egui/src/bindings/mod.rs index 52e10659ab..2f9299de79 100644 --- a/shards/egui/src/bindings/mod.rs +++ b/shards/egui/src/bindings/mod.rs @@ -1,6 +1,7 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); use egui::epaint; diff --git a/shards/egui/src/bindings/renderer.rs b/shards/egui/src/bindings/renderer.rs index eb2d122317..6443ab5de1 100644 --- a/shards/egui/src/bindings/renderer.rs +++ b/shards/egui/src/bindings/renderer.rs @@ -4,7 +4,7 @@ use super::*; use egui::epaint; use egui::ClippedPrimitive; use egui::Context; -use egui::TextureId; + use std::ffi::CString; use std::marker::PhantomData; use std::ptr; diff --git a/shards/egui/src/containers/area.rs b/shards/egui/src/containers/area.rs index eafe0a42c4..4c65736d3c 100644 --- a/shards/egui/src/containers/area.rs +++ b/shards/egui/src/containers/area.rs @@ -2,7 +2,7 @@ use crate::{ util::{self, with_possible_panic}, Anchor, EguiId, Order, CONTEXTS_NAME, PARENTS_UI_NAME, }; -use egui::{AreaState, Pos2, Rect, Vec2}; +use egui::{Pos2, Rect, Vec2}; use shards::{ core::register_shard, shard::Shard, diff --git a/shards/egui/src/containers/docking.rs b/shards/egui/src/containers/docking.rs index 1cf625375d..a5f601361c 100644 --- a/shards/egui/src/containers/docking.rs +++ b/shards/egui/src/containers/docking.rs @@ -397,7 +397,7 @@ impl LegacyShard for DockArea { Ok(()) } - fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, context: &Context, _input: &Var) -> Result, &str> { if self.tabs.surfaces_count() == 0 { return Ok(None); } diff --git a/shards/egui/src/containers/mod.rs b/shards/egui/src/containers/mod.rs index e24431d475..2983bc42ba 100644 --- a/shards/egui/src/containers/mod.rs +++ b/shards/egui/src/containers/mod.rs @@ -48,7 +48,6 @@ impl From for egui::Order { Order::Foreground => egui::Order::Foreground, Order::Tooltip => egui::Order::Tooltip, Order::Debug => egui::Order::Debug, - _ => unreachable!(), } } } diff --git a/shards/egui/src/egui_host.rs b/shards/egui/src/egui_host.rs index 0a5b82813d..69fb0a250d 100644 --- a/shards/egui/src/egui_host.rs +++ b/shards/egui/src/egui_host.rs @@ -1,4 +1,4 @@ -use std::mem::swap; + use super::util; use super::CONTEXTS_NAME; diff --git a/shards/egui/src/layouts/disable.rs b/shards/egui/src/layouts/disable.rs index dc17ecb31c..973e36884f 100644 --- a/shards/egui/src/layouts/disable.rs +++ b/shards/egui/src/layouts/disable.rs @@ -186,7 +186,9 @@ impl LegacyShard for Disable { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { ui.scope(|ui| { let disabled: bool = self.disable.get().try_into()?; - ui.set_enabled(!disabled); + if disabled { + ui.disable(); + } util::activate_ui_contents(context, input, ui, &mut self.parents, &mut self.contents) }) .inner?; diff --git a/shards/egui/src/layouts/frame.rs b/shards/egui/src/layouts/frame.rs index 341e0d3634..4a0bd808b2 100644 --- a/shards/egui/src/layouts/frame.rs +++ b/shards/egui/src/layouts/frame.rs @@ -85,12 +85,12 @@ impl Default for Frame { parents, requiring: Vec::new(), contents: ShardsVar::default(), - innerMargin: ParamVar::default(), - outerMargin: ParamVar::default(), + inner_margin: ParamVar::default(), + outer_margin: ParamVar::default(), rounding: ParamVar::default(), - fillColor: ParamVar::default(), - strokeColor: ParamVar::default(), - strokeWidth: ParamVar::default(), + fill_color: ParamVar::default(), + stroke_color: ParamVar::default(), + stroke_width: ParamVar::default(), exposing: Vec::new(), } } @@ -144,12 +144,12 @@ impl LegacyShard for Frame { fn setParam(&mut self, index: i32, value: &Var) -> Result<(), &str> { match index { 0 => self.contents.set_param(value), - 1 => self.innerMargin.set_param(value), - 2 => self.outerMargin.set_param(value), + 1 => self.inner_margin.set_param(value), + 2 => self.outer_margin.set_param(value), 3 => self.rounding.set_param(value), - 4 => self.fillColor.set_param(value), - 5 => self.strokeColor.set_param(value), - 6 => self.strokeWidth.set_param(value), + 4 => self.fill_color.set_param(value), + 5 => self.stroke_color.set_param(value), + 6 => self.stroke_width.set_param(value), _ => Err("Invalid parameter index"), } } @@ -157,12 +157,12 @@ impl LegacyShard for Frame { fn getParam(&mut self, index: i32) -> Var { match index { 0 => self.contents.get_param(), - 1 => self.innerMargin.get_param(), - 2 => self.outerMargin.get_param(), + 1 => self.inner_margin.get_param(), + 2 => self.outer_margin.get_param(), 3 => self.rounding.get_param(), - 4 => self.fillColor.get_param(), - 5 => self.strokeColor.get_param(), - 6 => self.strokeWidth.get_param(), + 4 => self.fill_color.get_param(), + 5 => self.stroke_color.get_param(), + 6 => self.stroke_width.get_param(), _ => Var::default(), } } @@ -195,8 +195,8 @@ impl LegacyShard for Frame { // Add UI.Parents to the list of required variables util::require_parents(&mut self.requiring); - if self.fillColor.is_variable() { - collect_required_variables(&data.shared, &mut self.requiring, (&self.fillColor).into()); + if self.fill_color.is_variable() { + collect_required_variables(&data.shared, &mut self.requiring, (&self.fill_color).into())?; } // Always passthrough the input @@ -208,23 +208,23 @@ impl LegacyShard for Frame { if !self.contents.is_empty() { self.contents.warmup(ctx)?; } - self.innerMargin.warmup(ctx); - self.outerMargin.warmup(ctx); + self.inner_margin.warmup(ctx); + self.outer_margin.warmup(ctx); self.rounding.warmup(ctx); - self.fillColor.warmup(ctx); - self.strokeColor.warmup(ctx); - self.strokeWidth.warmup(ctx); + self.fill_color.warmup(ctx); + self.stroke_color.warmup(ctx); + self.stroke_width.warmup(ctx); Ok(()) } fn cleanup(&mut self, ctx: Option<&Context>) -> Result<(), &str> { - self.strokeWidth.cleanup(ctx); - self.strokeColor.cleanup(ctx); - self.fillColor.cleanup(ctx); + self.stroke_width.cleanup(ctx); + self.stroke_color.cleanup(ctx); + self.fill_color.cleanup(ctx); self.rounding.cleanup(ctx); - self.outerMargin.cleanup(ctx); - self.innerMargin.cleanup(ctx); + self.outer_margin.cleanup(ctx); + self.inner_margin.cleanup(ctx); if !self.contents.is_empty() { self.contents.cleanup(ctx); } @@ -239,7 +239,7 @@ impl LegacyShard for Frame { } if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { - let inner_margin = self.innerMargin.get(); + let inner_margin = self.inner_margin.get(); let inner_margin = if inner_margin.is_none() { egui::Margin::default() } else { @@ -251,7 +251,7 @@ impl LegacyShard for Frame { bottom, } }; - let outer_margin = self.outerMargin.get(); + let outer_margin = self.outer_margin.get(); let outer_margin = if outer_margin.is_none() { egui::Margin::default() } else { @@ -270,7 +270,7 @@ impl LegacyShard for Frame { let (nw, ne, sw, se) = rounding.try_into()?; egui::epaint::Rounding { nw, ne, sw, se } }; - let fill: &shards::SHVar = self.fillColor.get(); + let fill: &shards::SHVar = self.fill_color.get(); let fill = if fill.is_none() { ui.style().visuals.widgets.noninteractive.bg_fill } else { @@ -278,13 +278,13 @@ impl LegacyShard for Frame { egui::Color32::from_rgba_unmultiplied(color.r, color.g, color.b, color.a) }; let stroke = { - let width = self.strokeWidth.get(); + let width = self.stroke_width.get(); let width = if width.is_none() { ui.style().visuals.widgets.noninteractive.bg_stroke.width } else { width.try_into()? }; - let color = self.strokeColor.get(); + let color = self.stroke_color.get(); let color = if color.is_none() { ui.style().visuals.widgets.noninteractive.bg_stroke.color } else { diff --git a/shards/egui/src/layouts/grid.rs b/shards/egui/src/layouts/grid.rs index 348705ace7..2ce12dd265 100644 --- a/shards/egui/src/layouts/grid.rs +++ b/shards/egui/src/layouts/grid.rs @@ -339,7 +339,7 @@ impl LegacyShard for NextRow { Ok(()) } - fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, _context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { ui.end_row(); diff --git a/shards/egui/src/layouts/layout.rs b/shards/egui/src/layouts/layout.rs index 1869f8feaa..8fa0241b3a 100644 --- a/shards/egui/src/layouts/layout.rs +++ b/shards/egui/src/layouts/layout.rs @@ -42,6 +42,8 @@ use shards::types::ANY_TYPES; use shards::types::BOOL_TYPES; use shards::types::BOOL_VAR_OR_NONE_SLICE; use shards::types::SHARDS_OR_NONE_TYPES; + + use std::rc::Rc; macro_rules! retrieve_layout_class_attribute { @@ -317,6 +319,20 @@ lazy_static! { ]; } +thread_local! { + static DEFAULT_LAYOUT_CLASS: LayoutClass = LayoutClass { + parent: std::ptr::null(), + layout: None, + min_size: None, + max_size: None, + fill_width: None, + fill_height: None, + disabled: None, + frame: None, + scroll_area: None, + }; +} + impl Default for LayoutConstructor { fn default() -> Self { Self { @@ -989,10 +1005,6 @@ impl Shard for LayoutShard { fn compose(&mut self, data: &InstanceData) -> Result { self.compose_helper(data)?; - if !self.layout_class.is_variable() { - return Err("Class parameter is required"); - } - self.inner_exposed.clear(); if !self.contents.is_empty() { let composed = self.contents.compose(data)?; @@ -1009,156 +1021,183 @@ impl Shard for LayoutShard { } fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { - let ui = util::get_parent_ui(self.parents.get())?; - let layout_class: Option> = Some(Var::from_object_as_clone( - self.layout_class.get(), - &LAYOUTCLASS_TYPE, - )?); - let layout_class = unsafe { - let layout_ptr = Rc::as_ptr(layout_class.as_ref().unwrap()) as *mut LayoutClass; - &*layout_ptr - }; - - let layout = if let Some(layout) = retrieve_layout_class_attribute!(layout_class, layout) { - layout - } else { - return Err("No layout found in LayoutClass. LayoutClass is invalid/corrupted"); - }; - - let min_size = if !self.min_size.get().is_none() { - self.min_size.get().try_into()? - } else { - if let Some(min_size) = retrieve_layout_class_attribute!(layout_class, min_size) { - min_size + DEFAULT_LAYOUT_CLASS.with(|default_layout| { + let ui = util::get_parent_ui(self.parents.get())?; + let lc_var = self.layout_class.get(); + let layout_class = if !lc_var.is_none() { + let r: Option> = + Some(Var::from_object_as_clone(lc_var, &LAYOUTCLASS_TYPE)?); + unsafe { + let layout_ptr = Rc::as_ptr(r.as_ref().unwrap()) as *mut LayoutClass; + &*layout_ptr + } } else { - (0.0, 0.0) // default value for min_size - } - }; + default_layout + }; - let max_size = if !self.max_size.get().is_none() { - Some(self.max_size.get().try_into()?) - } else { - if let Some(max_size) = retrieve_layout_class_attribute!(layout_class, max_size) { - Some(max_size) + let layout = if let Some(layout) = retrieve_layout_class_attribute!(layout_class, layout) { + layout } else { - None // default value for max_size (no max size) - } - }; + ui.layout().clone() + }; - // shard parameters have higher priority and override layout class - let fill_width = if !self.fill_width.get().is_none() { - self.fill_width.get().try_into()? - } else { - if let Some(fill_width) = retrieve_layout_class_attribute!(layout_class, fill_width) { - fill_width + let min_size = if !self.min_size.get().is_none() { + self.min_size.get().try_into()? } else { - false // default value for fill_width - } - }; + if let Some(min_size) = retrieve_layout_class_attribute!(layout_class, min_size) { + min_size + } else { + (0.0, 0.0) // default value for min_size + } + }; - let fill_height = if !self.fill_height.get().is_none() { - self.fill_height.get().try_into()? - } else { - if let Some(fill_height) = retrieve_layout_class_attribute!(layout_class, fill_height) { - fill_height + let max_size = if !self.max_size.get().is_none() { + Some(self.max_size.get().try_into()?) } else { - false // default value for fill_height - } - }; + if let Some(max_size) = retrieve_layout_class_attribute!(layout_class, max_size) { + Some(max_size) + } else { + None // default value for max_size (no max size) + } + }; - let mut min_size: egui::Vec2 = min_size.into(); + // shard parameters have higher priority and override layout class + let fill_width = if !self.fill_width.get().is_none() { + self.fill_width.get().try_into()? + } else { + if let Some(fill_width) = retrieve_layout_class_attribute!(layout_class, fill_width) { + fill_width + } else { + false // default value for fill_width + } + }; - let mut max_size = if let Some(max_size) = max_size { - egui::Vec2::new(max_size.0, max_size.1) - } else { - ui.available_size_before_wrap() // try to take up all available space (no max) - }; + let fill_height = if !self.fill_height.get().is_none() { + self.fill_height.get().try_into()? + } else { + if let Some(fill_height) = retrieve_layout_class_attribute!(layout_class, fill_height) { + fill_height + } else { + false // default value for fill_height + } + }; - if fill_width { - min_size.x = ui.available_size_before_wrap().x; - max_size.x = ui.available_size_before_wrap().x; - } - if fill_height { - min_size.y = ui.available_size_before_wrap().y; - max_size.y = ui.available_size_before_wrap().y; - } + let mut min_size: egui::Vec2 = min_size.into(); - // If the size is still 0, use only minimum size for an interactive widget - if min_size.x == 0.0 { - min_size.x = ui.spacing().interact_size.x; - } - if min_size.y == 0.0 { - min_size.y = ui.spacing().interact_size.y; - } + let mut max_size = if let Some(max_size) = max_size { + egui::Vec2::new(max_size.0, max_size.1) + } else { + ui.available_size_before_wrap() // try to take up all available space (no max) + }; - let disabled = if let Some(disabled) = retrieve_layout_class_attribute!(layout_class, disabled) - { - disabled - } else { - false // default value for disabled - }; + if fill_width { + min_size.x = ui.available_size_before_wrap().x; + max_size.x = ui.available_size_before_wrap().x; + } + if fill_height { + min_size.y = ui.available_size_before_wrap().y; + max_size.y = ui.available_size_before_wrap().y; + } - let frame = if let Some(frame) = retrieve_layout_class_attribute!(layout_class, frame) { - let style = ui.style(); - match frame { - LayoutFrame::Widgets => Some(egui::Frame::group(style)), - LayoutFrame::SideTopPanel => Some(egui::Frame::side_top_panel(style)), - LayoutFrame::CentralPanel => Some(egui::Frame::central_panel(style)), - LayoutFrame::Window => Some(egui::Frame::window(style)), - LayoutFrame::Menu => Some(egui::Frame::menu(style)), - LayoutFrame::Popup => Some(egui::Frame::popup(style)), - LayoutFrame::Canvas => Some(egui::Frame::canvas(style)), - LayoutFrame::DarkCanvas => Some(egui::Frame::dark_canvas(style)), - _ => unreachable!(), + // If the size is still 0, use only minimum size for an interactive widget + if min_size.x == 0.0 { + min_size.x = ui.spacing().interact_size.x; + } + if min_size.y == 0.0 { + min_size.y = ui.spacing().interact_size.y; } - } else { - None // default value for frame - }; - let scroll_area = - if let Some(scroll_area) = retrieve_layout_class_attribute!(layout_class, scroll_area) { - Some(scroll_area.to_egui_scrollarea()) + let disabled = + if let Some(disabled) = retrieve_layout_class_attribute!(layout_class, disabled) { + disabled + } else { + false // default value for disabled + }; + + let frame = if let Some(frame) = retrieve_layout_class_attribute!(layout_class, frame) { + let style = ui.style(); + match frame { + LayoutFrame::Widgets => Some(egui::Frame::group(style)), + LayoutFrame::SideTopPanel => Some(egui::Frame::side_top_panel(style)), + LayoutFrame::CentralPanel => Some(egui::Frame::central_panel(style)), + LayoutFrame::Window => Some(egui::Frame::window(style)), + LayoutFrame::Menu => Some(egui::Frame::menu(style)), + LayoutFrame::Popup => Some(egui::Frame::popup(style)), + LayoutFrame::Canvas => Some(egui::Frame::canvas(style)), + LayoutFrame::DarkCanvas => Some(egui::Frame::dark_canvas(style)), + } } else { - None // default value for scroll_area + None // default value for frame }; - let scroll_area_id = EguiId::new(self, 0); // TODO: Check if have scroll area first + let scroll_area = + if let Some(scroll_area) = retrieve_layout_class_attribute!(layout_class, scroll_area) { + Some(scroll_area.to_egui_scrollarea()) + } else { + None // default value for scroll_area + }; - if self.contents.is_empty() { - return Ok(None); - } + let scroll_area_id = EguiId::new(self, 0); // TODO: Check if have scroll area first + + if self.contents.is_empty() { + return Ok(None); + } - with_possible_panic(|| { - // if there is a frame, draw it as the outermost ui element - if let Some(frame) = frame { - frame - .show(ui, |ui| { - // set whether all widgets in the contents are enabled or disabled - ui.add_enabled_ui(!disabled, |ui| { - // add the new child_ui created by frame onto the stack of parents - // inside of frame - // render scroll area and inner layout if there is a scroll area - if let Some(scroll_area) = scroll_area { - scroll_area - .id_source(scroll_area_id) - .show(ui, |ui| { - // inside of scroll area - ui.allocate_ui_with_layout(max_size, layout, |ui| { - ui.set_min_size(min_size); // set minimum size of entire layout - - util::activate_ui_contents( - context, - input, - ui, - &mut self.parents, - &mut self.contents, - ) + with_possible_panic(|| { + // if there is a frame, draw it as the outermost ui element + if let Some(frame) = frame { + frame + .show(ui, |ui| { + // set whether all widgets in the contents are enabled or disabled + ui.add_enabled_ui(!disabled, |ui| { + // add the new child_ui created by frame onto the stack of parents + // inside of frame + // render scroll area and inner layout if there is a scroll area + if let Some(scroll_area) = scroll_area { + scroll_area + .id_source(scroll_area_id) + .show(ui, |ui| { + // inside of scroll area + ui.allocate_ui_with_layout(max_size, layout, |ui| { + ui.set_min_size(min_size); // set minimum size of entire layout + + util::activate_ui_contents( + context, + input, + ui, + &mut self.parents, + &mut self.contents, + ) + }) + .inner }) .inner + } else { + // inside of frame, no scroll area to render, render inner layout + ui.allocate_ui_with_layout(max_size, layout, |ui| { + ui.set_min_size(min_size); // set minimum size of entire layout + + util::activate_ui_contents( + context, + input, + ui, + &mut self.parents, + &mut self.contents, + ) }) .inner - } else { - // inside of frame, no scroll area to render, render inner layout + } + }) + .inner + }) + .inner + } else { + // no frame to render, render only the scroll area (if applicable) and inner layout + if let Some(scroll_area) = scroll_area { + scroll_area + .id_source(scroll_area_id) + .show(ui, |ui| { + // inside of scroll area ui.allocate_ui_with_layout(max_size, layout, |ui| { ui.set_min_size(min_size); // set minimum size of entire layout @@ -1171,46 +1210,23 @@ impl Shard for LayoutShard { ) }) .inner - } - }) - .inner - }) - .inner - } else { - // no frame to render, render only the scroll area (if applicable) and inner layout - if let Some(scroll_area) = scroll_area { - scroll_area - .id_source(scroll_area_id) - .show(ui, |ui| { - // inside of scroll area - ui.allocate_ui_with_layout(max_size, layout, |ui| { - ui.set_min_size(min_size); // set minimum size of entire layout - - util::activate_ui_contents( - context, - input, - ui, - &mut self.parents, - &mut self.contents, - ) }) .inner + } else { + // inside of frame, no scroll area to render, render inner layout + ui.allocate_ui_with_layout(max_size, layout, |ui| { + ui.set_min_size(min_size); // set minimum size of entire layout + + util::activate_ui_contents(context, input, ui, &mut self.parents, &mut self.contents) }) .inner - } else { - // inside of frame, no scroll area to render, render inner layout - ui.allocate_ui_with_layout(max_size, layout, |ui| { - ui.set_min_size(min_size); // set minimum size of entire layout - - util::activate_ui_contents(context, input, ui, &mut self.parents, &mut self.contents) - }) - .inner + } } - } - })??; + })??; - // Always passthrough the input - Ok(None) + // Always passthrough the input + Ok(None) + }) } } diff --git a/shards/egui/src/layouts/mod.rs b/shards/egui/src/layouts/mod.rs index 150cb274cc..5ea292b227 100644 --- a/shards/egui/src/layouts/mod.rs +++ b/shards/egui/src/layouts/mod.rs @@ -3,6 +3,7 @@ use shards::core::register_enum; use shards::core::register_legacy_shard; + use shards::types::common_type; use shards::types::ClonedVar; use shards::types::ExposedTypes; @@ -39,12 +40,12 @@ struct Disable { struct Frame { parents: ParamVar, requiring: ExposedTypes, - innerMargin: ParamVar, - outerMargin: ParamVar, + inner_margin: ParamVar, + outer_margin: ParamVar, rounding: ParamVar, - fillColor: ParamVar, - strokeColor: ParamVar, - strokeWidth: ParamVar, + fill_color: ParamVar, + stroke_color: ParamVar, + stroke_width: ParamVar, contents: ShardsVar, exposing: ExposedTypes, } @@ -238,16 +239,6 @@ struct NextRow { requiring: ExposedTypes, } -struct ScrollArea { - parents: ParamVar, - requiring: ExposedTypes, - contents: ShardsVar, - horizontal: ParamVar, - vertical: ParamVar, - alwaysShow: ParamVar, - exposing: ExposedTypes, -} - struct Separator { parents: ParamVar, requiring: ExposedTypes, @@ -311,6 +302,8 @@ mod vertical; pub fn register_shards() { auto_grid::register_shards(); layout::register_shards(); + table::register_shards(); + scroll_area::register_shards(); register_legacy_shard::(); register_legacy_shard::(); register_legacy_shard::(); @@ -324,10 +317,8 @@ pub fn register_shards() { register_enum::(); register_legacy_shard::(); register_legacy_shard::(); - register_legacy_shard::(); register_legacy_shard::(); register_legacy_shard::(); - register_legacy_shard::(); register_legacy_shard::(); register_legacy_shard::(); } diff --git a/shards/egui/src/layouts/scroll_area.rs b/shards/egui/src/layouts/scroll_area.rs index 7d8dcc1437..d994cc2ccf 100644 --- a/shards/egui/src/layouts/scroll_area.rs +++ b/shards/egui/src/layouts/scroll_area.rs @@ -1,54 +1,55 @@ /* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright © 2022 Fragcolor Pte. Ltd. */ -use super::ScrollArea; use crate::util; -use crate::util::with_possible_panic; use crate::EguiId; -use crate::HELP_OUTPUT_EQUAL_INPUT; +use crate::FLOAT_VAR_OR_NONE_SLICE; use crate::PARENTS_UI_NAME; +use shards::core::register_shard; use shards::shard::LegacyShard; +use shards::shard::Shard; use shards::types::Context; use shards::types::ExposedTypes; use shards::types::InstanceData; -use shards::types::OptionalString; use shards::types::ParamVar; -use shards::types::Parameters; use shards::types::ShardsVar; use shards::types::Type; use shards::types::Types; use shards::types::Var; use shards::types::ANY_TYPES; -use shards::types::BOOL_TYPES; +use shards::types::BOOL_VAR_OR_NONE_SLICE; use shards::types::SHARDS_OR_NONE_TYPES; -lazy_static! { - static ref AREA_PARAMETERS: Parameters = vec![ - ( - cstr!("Contents"), - shccstr!("The UI contents."), - &SHARDS_OR_NONE_TYPES[..], - ) - .into(), - ( - cstr!("Horizontal"), - shccstr!("Enable horizontal scrolling."), - &BOOL_TYPES[..], - ) - .into(), - ( - cstr!("Vertical"), - shccstr!("Enable vertical scrolling."), - &BOOL_TYPES[..], - ) - .into(), - ( - cstr!("AlwaysShow"), - shccstr!("Always show the enabled scroll bars even if not needed."), - &BOOL_TYPES[..], - ) - .into(), - ]; +#[derive(shards::shard)] +#[shard_info("UI.ScrollArea", "Add scrolling to contained UI elements.")] +pub struct ScrollArea { + #[shard_required] + requiring: ExposedTypes, + inner_exposed: ExposedTypes, + #[shard_warmup] + parents: ParamVar, + #[shard_param("Contents", "The UI contents to scroll.", SHARDS_OR_NONE_TYPES)] + contents: ShardsVar, + #[shard_param("Horizontal", "Enable horizontal scrolling.", BOOL_VAR_OR_NONE_SLICE)] + horizontal: ParamVar, + #[shard_param("Vertical", "Enable vertical scrolling.", BOOL_VAR_OR_NONE_SLICE)] + vertical: ParamVar, + #[shard_param( + "AlwaysShow", + "Always show the enabled scroll bars even if not needed.", + BOOL_VAR_OR_NONE_SLICE + )] + always_show: ParamVar, + #[shard_param( + "AutoShrink", + "Whether to automatically shrink the scroll area.", + BOOL_VAR_OR_NONE_SLICE + )] + auto_shrink: ParamVar, + #[shard_param("MaxHeight", "Maximum height of scroll area.", FLOAT_VAR_OR_NONE_SLICE)] + max_height: ParamVar, + #[shard_param("MaxWidth", "Maximum width of scroll area.", FLOAT_VAR_OR_NONE_SLICE)] + max_width: ParamVar, } impl Default for ScrollArea { @@ -56,168 +57,96 @@ impl Default for ScrollArea { let mut parents = ParamVar::default(); parents.set_name(PARENTS_UI_NAME); Self { + requiring: ExposedTypes::new(), + inner_exposed: ExposedTypes::new(), parents, - requiring: Vec::new(), contents: ShardsVar::default(), - horizontal: ParamVar::new(false.into()), - vertical: ParamVar::new(true.into()), - alwaysShow: ParamVar::new(false.into()), - exposing: Vec::new(), + horizontal: ParamVar::new(Var::new_bool(false)), + vertical: ParamVar::new(Var::new_bool(true)), + always_show: ParamVar::new(Var::new_bool(false)), + auto_shrink: ParamVar::new(Var::new_bool(false)), + max_height: ParamVar::default(), + max_width: ParamVar::default(), } } } -impl LegacyShard for ScrollArea { - fn registerName() -> &'static str - where - Self: Sized, - { - cstr!("UI.ScrollArea") - } - - fn hash() -> u32 - where - Self: Sized, - { - compile_time_crc32::crc32!("UI.ScrollArea-rust-0x20200101") - } - - fn name(&mut self) -> &str { - "UI.ScrollArea" - } - - fn help(&mut self) -> OptionalString { - OptionalString(shccstr!( - "Add vertical and/or horizontal scrolling to a contained UI." - )) - } - - fn inputTypes(&mut self) -> &Types { +#[shards::shard_impl] +impl Shard for ScrollArea { + fn input_types(&mut self) -> &Types { &ANY_TYPES } - fn inputHelp(&mut self) -> OptionalString { - OptionalString(shccstr!( - "The value that will be passed to the Contents shards of the scroll area." - )) - } - - fn outputTypes(&mut self) -> &Types { + fn output_types(&mut self) -> &Types { &ANY_TYPES } - fn outputHelp(&mut self) -> OptionalString { - *HELP_OUTPUT_EQUAL_INPUT - } - - fn parameters(&mut self) -> Option<&Parameters> { - Some(&AREA_PARAMETERS) - } - - fn setParam(&mut self, index: i32, value: &Var) -> Result<(), &str> { - match index { - 0 => self.contents.set_param(value), - 1 => self.horizontal.set_param(value), - 2 => self.vertical.set_param(value), - 3 => self.alwaysShow.set_param(value), - _ => Err("Invalid parameter index"), - } + fn warmup(&mut self, context: &Context) -> Result<(), &str> { + self.warmup_helper(context)?; + Ok(()) } - fn getParam(&mut self, index: i32) -> Var { - match index { - 0 => self.contents.get_param(), - 1 => self.horizontal.get_param(), - 2 => self.vertical.get_param(), - 3 => self.alwaysShow.get_param(), - _ => Var::default(), - } + fn cleanup(&mut self, ctx: Option<&Context>) -> Result<(), &str> { + self.cleanup_helper(ctx)?; + Ok(()) } - fn requiredVariables(&mut self) -> Option<&ExposedTypes> { - self.requiring.clear(); - - // Add UI.Parents to the list of required variables + fn compose(&mut self, data: &InstanceData) -> Result { + self.compose_helper(data)?; util::require_parents(&mut self.requiring); - Some(&self.requiring) - } - - fn exposedVariables(&mut self) -> Option<&ExposedTypes> { - self.exposing.clear(); - - if util::expose_contents_variables(&mut self.exposing, &self.contents) { - Some(&self.exposing) - } else { - None - } - } - - fn hasCompose() -> bool { - true - } - - fn compose(&mut self, data: &InstanceData) -> Result { if !self.contents.is_empty() { - self.contents.compose(data)?; + let composed = self.contents.compose(data)?; + shards::util::merge_exposed_types(&mut self.inner_exposed, &composed.exposedInfo); + shards::util::merge_exposed_types(&mut self.requiring, &composed.requiredInfo); } - // Always passthrough the input Ok(data.inputType) } - fn warmup(&mut self, ctx: &Context) -> Result<(), &str> { - self.parents.warmup(ctx); - if !self.contents.is_empty() { - self.contents.warmup(ctx)?; - } - self.horizontal.warmup(ctx); - self.vertical.warmup(ctx); - self.alwaysShow.warmup(ctx); + fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { + let ui = util::get_parent_ui(self.parents.get())?; - Ok(()) - } + let mut scroll_area = egui::ScrollArea::new([ + self.horizontal.get().try_into()?, + self.vertical.get().try_into()?, + ]); - fn cleanup(&mut self, ctx: Option<&Context>) -> Result<(), &str> { - self.alwaysShow.cleanup(ctx); - self.vertical.cleanup(ctx); - self.horizontal.cleanup(ctx); - if !self.contents.is_empty() { - self.contents.cleanup(ctx); - } - self.parents.cleanup(ctx); + // Configure scroll area + scroll_area = scroll_area.id_source(EguiId::new(self, 0)); - Ok(()) - } - - fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { - if self.contents.is_empty() { - return Ok(None); + if !self.max_width.get().is_none() { + scroll_area = scroll_area.max_width(self.max_width.get().try_into()?); + } + if !self.max_height.get().is_none() { + scroll_area = scroll_area.max_height(self.max_height.get().try_into()?); } - if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { - with_possible_panic(|| { - let visibility = if self.alwaysShow.get().try_into()? { - egui::scroll_area::ScrollBarVisibility::AlwaysVisible - } else { - egui::scroll_area::ScrollBarVisibility::VisibleWhenNeeded - }; - egui::ScrollArea::new([ - self.horizontal.get().try_into()?, - self.vertical.get().try_into()?, - ]) - .id_source(EguiId::new(self, 0)) - .scroll_bar_visibility(visibility) - .show(ui, |ui| { - util::activate_ui_contents(context, input, ui, &mut self.parents, &mut self.contents) - }) - .inner - })??; - - // Always passthrough the input - Ok(None) + let auto_shrink: bool = if !self.auto_shrink.get().is_none() { + self.auto_shrink.get().try_into()? } else { - Err("No UI parent") - } + false + }; + scroll_area = scroll_area.auto_shrink([auto_shrink; 2]); + + let visibility = if self.always_show.get().try_into()? { + egui::scroll_area::ScrollBarVisibility::AlwaysVisible + } else { + egui::scroll_area::ScrollBarVisibility::VisibleWhenNeeded + }; + scroll_area = scroll_area.scroll_bar_visibility(visibility); + + // Show scroll area with contents + scroll_area + .show(ui, |ui| { + util::activate_ui_contents(context, input, ui, &mut self.parents, &mut self.contents) + }) + .inner?; + + Ok(Some(input.clone())) } } + +pub(crate) fn register_shards() { + register_shard::(); +} diff --git a/shards/egui/src/layouts/separator.rs b/shards/egui/src/layouts/separator.rs index 946a667c4b..64493e6ad0 100644 --- a/shards/egui/src/layouts/separator.rs +++ b/shards/egui/src/layouts/separator.rs @@ -88,7 +88,7 @@ impl LegacyShard for Separator { Ok(()) } - fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, _context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { ui.separator(); diff --git a/shards/egui/src/layouts/sized.rs b/shards/egui/src/layouts/sized.rs index aa1322f325..738ddfc25a 100644 --- a/shards/egui/src/layouts/sized.rs +++ b/shards/egui/src/layouts/sized.rs @@ -5,8 +5,8 @@ use crate::util; use crate::HELP_OUTPUT_EQUAL_INPUT; use crate::PARENTS_UI_NAME; use egui::vec2; -use egui::Align; -use egui::Layout; + + use egui::Ui; use egui::Vec2; use shards::shard::LegacyShard; diff --git a/shards/egui/src/layouts/space.rs b/shards/egui/src/layouts/space.rs index 709765be16..802df7cfff 100644 --- a/shards/egui/src/layouts/space.rs +++ b/shards/egui/src/layouts/space.rs @@ -120,7 +120,7 @@ impl LegacyShard for Space { Ok(()) } - fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, _context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { ui.add_space(self.amount.get().try_into()?); diff --git a/shards/egui/src/layouts/table.rs b/shards/egui/src/layouts/table.rs index a94015d737..ba5e3c8fe6 100644 --- a/shards/egui/src/layouts/table.rs +++ b/shards/egui/src/layouts/table.rs @@ -1,456 +1,740 @@ /* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright © 2022 Fragcolor Pte. Ltd. */ -use super::Table; use crate::util; use crate::EguiId; +use crate::BOOL_VAR_SLICE; +use crate::FLOAT_VAR_OR_NONE_SLICE; use crate::HELP_OUTPUT_EQUAL_INPUT; -use crate::INT_VAR_OR_NONE_SLICE; + use crate::PARENTS_UI_NAME; -use shards::shard::LegacyShard; -use shards::shardsc::SHType_Int; -use shards::shardsc::SHType_Seq; -use shards::shardsc::SHType_ShardRef; -use shards::shardsc::SHType_String; + +use shards::core::register_shard; +use shards::shard::Shard; + +use shards::shardsc::SHType_Seq as SHTYPE_SEQ; + use shards::types::common_type; use shards::types::ClonedVar; use shards::types::Context; -use shards::types::ExposedInfo; + use shards::types::ExposedTypes; use shards::types::InstanceData; use shards::types::OptionalString; use shards::types::ParamVar; -use shards::types::Parameters; + use shards::types::Seq; use shards::types::ShardsVar; use shards::types::Type; use shards::types::Types; use shards::types::Var; -use shards::types::ANYS_TYPES; +use shards::types::WireState; +use shards::types::ANY_TYPES; use shards::types::BOOL_VAR_OR_NONE_SLICE; -use shards::types::SEQ_OF_ANY_TABLE_TYPES; -use shards::types::SEQ_OF_SHARDS_TYPES; -use shards::util::from_raw_parts_allow_null; -use std::cmp::Ordering; -use std::ffi::CStr; - -lazy_static! { - static ref TABLE_PARAMETERS: Parameters = vec![ - ( - cstr!("Builder"), - shccstr!("Sequence of shards to build each column, repeated for each row."), - &SEQ_OF_SHARDS_TYPES[..] - ) - .into(), - ( - cstr!("Columns"), - shccstr!("Configuration of the columns."), - &SEQ_OF_ANY_TABLE_TYPES[..] - ) - .into(), - ( - cstr!("Striped"), - shccstr!("Whether to alternate a subtle background color to every other row."), - BOOL_VAR_OR_NONE_SLICE, - ) - .into(), - ( - cstr!("Resizable"), - shccstr!("Whether columns can be resized within their specified range."), - BOOL_VAR_OR_NONE_SLICE, - ) - .into(), - ( - cstr!("RowIndex"), - shccstr!("Variable to hold the row index, to be used within Rows."), - INT_VAR_OR_NONE_SLICE, - ) - .into(), - ]; + +use shards::types::STRING_VAR_OR_NONE_SLICE; + +use std::cell::RefCell; + +// Thread-local context for Table composition +thread_local! { + static TABLE_CONTEXT: RefCell = RefCell::new(TableContext::default()); } -impl Default for Table { +// Struct to hold column settings +struct ColumnSettings { + pub width_type: ParamVar, // "auto", "initial", "exact", "remainder" + pub width_value: ParamVar, // Value for initial/exact + pub min_width: ParamVar, // Minimum width + pub max_width: ParamVar, // Maximum width + pub clip: ParamVar, // Whether to clip content + pub resizable: ParamVar, // Whether column is resizable +} + +impl ColumnSettings { + fn warmup(&mut self, ctx: &Context) { + self.width_type.warmup(ctx); + self.width_value.warmup(ctx); + self.min_width.warmup(ctx); + self.max_width.warmup(ctx); + self.clip.warmup(ctx); + self.resizable.warmup(ctx); + } + + fn compose(&mut self, data: &InstanceData, out_exp: &mut ExposedTypes) -> Result { + shards::util::collect_required_variables(&data.shared, out_exp, (&self.width_type).into())?; + shards::util::collect_required_variables(&data.shared, out_exp, (&self.width_value).into())?; + shards::util::collect_required_variables(&data.shared, out_exp, (&self.min_width).into())?; + shards::util::collect_required_variables(&data.shared, out_exp, (&self.max_width).into())?; + shards::util::collect_required_variables(&data.shared, out_exp, (&self.clip).into())?; + shards::util::collect_required_variables(&data.shared, out_exp, (&self.resizable).into())?; + Ok(data.inputType) + } + + fn cleanup(&mut self, ctx: Option<&Context>) { + self.width_type.cleanup(ctx); + self.width_value.cleanup(ctx); + self.min_width.cleanup(ctx); + self.max_width.cleanup(ctx); + self.clip.cleanup(ctx); + } +} + +struct TableContext { + current_header: Option, + current_column_settings: Option, + in_table_compose: bool, +} + +impl Default for TableContext { fn default() -> Self { - let mut parents = ParamVar::default(); - parents.set_name(PARENTS_UI_NAME); - let mut row_index = ParamVar::default(); - row_index.set_name("Table.RowIndex"); Self { - parents, - requiring: Vec::new(), - rows: Seq::new().as_ref().into(), - columns: ClonedVar::default(), - striped: ParamVar::default(), - resizable: ParamVar::default(), - row_index, - shards: Vec::new(), - header_shards: Vec::new(), - exposing: Vec::new(), + current_header: None, + current_column_settings: None, + in_table_compose: false, } } } -impl LegacyShard for Table { - fn registerName() -> &'static str - where - Self: Sized, - { - cstr!("UI.Table") +// Helper functions for context management +impl TableContext { + fn enter_table_compose() { + TABLE_CONTEXT.with(|ctx| { + let mut ctx = ctx.borrow_mut(); + ctx.in_table_compose = true; + ctx.current_header = None; + ctx.current_column_settings = None; + }); } - fn hash() -> u32 - where - Self: Sized, - { - compile_time_crc32::crc32!("UI.Table-rust-0x20200101") + fn exit_table_compose() { + TABLE_CONTEXT.with(|ctx| { + let mut ctx = ctx.borrow_mut(); + ctx.in_table_compose = false; + ctx.current_header = None; + ctx.current_column_settings = None; + }); } - fn name(&mut self) -> &str { - "UI.Table" + fn set_header(header: ShardsVar, settings: Option) -> Result<(), &'static str> { + TABLE_CONTEXT.with(|ctx| { + let mut ctx = ctx.borrow_mut(); + if !ctx.in_table_compose { + return Err("UI.Header can only be used within UI.Table columns"); + } + if ctx.current_header.is_some() { + return Err("Only one UI.Header allowed per column"); + } + ctx.current_header = Some(header); + ctx.current_column_settings = settings; + Ok(()) + }) } - fn help(&mut self) -> OptionalString { - OptionalString(shccstr!("Table layout.")) + fn take_header() -> (Option, Option) { + TABLE_CONTEXT.with(|ctx| { + let mut ctx = ctx.borrow_mut(); + ( + ctx.current_header.take(), + ctx.current_column_settings.take(), + ) + }) } - fn inputTypes(&mut self) -> &Types { - &ANYS_TYPES + fn is_inside_table_compose() -> bool { + TABLE_CONTEXT.with(|ctx| { + let ctx = ctx.borrow(); + ctx.in_table_compose + }) } +} + +lazy_static::lazy_static! { + static ref SHARDS_OR_NONE_TYPES: Vec = vec![common_type::shard, common_type::shards]; + static ref SEQ_OF_SHARDS: Type = Type::seq(&SHARDS_OR_NONE_TYPES); + static ref SEQ_OF_SHARDS_TYPES: Vec = vec![*SEQ_OF_SHARDS]; + + static ref ANY_SEQ: Type = Type::seq(&ANY_TYPES); + static ref INPUT_TYPES: Vec = vec![common_type::int, *ANY_SEQ]; +} - fn inputHelp(&mut self) -> OptionalString { +#[derive(shards::shard)] +#[shard_info("UI.Table", "Table layout.")] +pub struct Table { + #[shard_required] + requiring: ExposedTypes, + inner_exposed: ExposedTypes, + #[shard_warmup] + parents: ParamVar, + #[shard_param( + "Columns", + "Column definitions with headers and content.", + SEQ_OF_SHARDS_TYPES + )] + columns: ClonedVar, + #[shard_param( + "Striped", + "Whether to alternate a subtle background color to every other row.", + BOOL_VAR_SLICE + )] + striped: ParamVar, + #[shard_param( + "Resizable", + "Whether columns can be resized within their specified range.", + BOOL_VAR_SLICE + )] + resizable: ParamVar, + #[shard_param("Reversed", "Whether the table is reversed.", BOOL_VAR_SLICE)] + reversed: ParamVar, + column_shards: Vec, + header_shards: Vec>, + #[shard_param( + "IsSelected", + "Callback function for checking if a row is currently selected.", + SHARDS_OR_NONE_TYPES + )] + is_selected_callback: ShardsVar, + #[shard_param( + "Clicked", + "Callback function for when a row is clicked.", + SHARDS_OR_NONE_TYPES + )] + clicked_callback: ShardsVar, + #[shard_param( + "DoubleClicked", + "Callback function for when a row is double-clicked.", + SHARDS_OR_NONE_TYPES + )] + double_clicked_callback: ShardsVar, + #[shard_param( + "ContextMenu", + "Callback function for the right-click context menu on rows.", + SHARDS_OR_NONE_TYPES + )] + context_menu: ShardsVar, + #[shard_param( + "RowHeight", + "Height of each row in pixels. Default is text height.", + FLOAT_VAR_OR_NONE_SLICE + )] + row_height: ParamVar, + can_interact: bool, + remap_key_seq: bool, + column_settings: Vec>, +} + +impl Default for Table { + fn default() -> Self { + let mut parents = ParamVar::default(); + parents.set_name(PARENTS_UI_NAME); + Self { + requiring: ExposedTypes::new(), + inner_exposed: ExposedTypes::new(), + parents, + columns: ClonedVar::default(), + striped: ParamVar::new(Var::new_bool(false)), + resizable: ParamVar::new(Var::new_bool(false)), + reversed: ParamVar::new(Var::new_bool(false)), + column_shards: Vec::new(), + header_shards: Vec::new(), + is_selected_callback: ShardsVar::default(), + clicked_callback: ShardsVar::default(), + double_clicked_callback: ShardsVar::default(), + context_menu: ShardsVar::default(), + row_height: ParamVar::default(), + can_interact: false, + remap_key_seq: false, + column_settings: Vec::new(), + } + } +} + +#[shards::shard_impl] +impl Shard for Table { + fn input_types(&mut self) -> &Types { + &INPUT_TYPES + } + + fn input_help(&mut self) -> OptionalString { OptionalString(shccstr!( - "The value that will be passed to the Columns and Rows shards of the table." + "The values that will be passed to the Columns and Rows shards of the table, or number of items in the sequence." )) } - fn outputTypes(&mut self) -> &Types { - &ANYS_TYPES + fn output_types(&mut self) -> &Types { + &INPUT_TYPES } - fn outputHelp(&mut self) -> OptionalString { + fn output_help(&mut self) -> OptionalString { *HELP_OUTPUT_EQUAL_INPUT } - fn parameters(&mut self) -> Option<&Parameters> { - Some(&TABLE_PARAMETERS) + fn exposed_variables(&mut self) -> Option<&ExposedTypes> { + Some(&self.inner_exposed) } - fn setParam(&mut self, index: i32, value: &Var) -> Result<(), &str> { - match index { - 0 => { - self.shards.clear(); - - let seq = Seq::try_from(value)?; - - for shard in seq.iter() { - let mut s = ShardsVar::default(); - s.set_param(&shard)?; - self.shards.push(s); - } + fn warmup(&mut self, ctx: &Context) -> Result<(), &str> { + self.warmup_helper(ctx)?; - Ok(self.rows = value.into()) + for s in &mut self.column_settings { + if let Some(s) = s { + s.warmup(ctx); } - 1 => { - self.header_shards.clear(); - - if let Ok(columns) = Seq::try_from(value) { - for column in columns.iter() { - let column: shards::types::Table = column.as_ref().try_into()?; - if let Some(header) = column.get_static("Header") { - match header.valueType { - SHType_String => { - self.header_shards.push(None); - } - SHType_ShardRef | SHType_Seq => { - let mut s = ShardsVar::default(); - s.set_param(&header)?; - self.header_shards.push(Some(s)); - } - _ => unreachable!(), - } - } else { - self.header_shards.push(None); - } - } - } + } - Ok(self.columns = value.into()) + for s in &mut self.column_shards { + s.warmup(ctx)?; + } + for s in &mut self.header_shards { + if let Some(s) = s { + s.warmup(ctx)?; } - 2 => self.striped.set_param(value), - 3 => self.resizable.set_param(value), - 4 => self.row_index.set_param(value), - _ => Err("Invalid parameter index"), } - } - fn getParam(&mut self, index: i32) -> Var { - match index { - 0 => self.rows.0, - 1 => self.columns.0, - 2 => self.striped.get_param(), - 3 => self.resizable.get_param(), - 4 => self.row_index.get_param(), - _ => Var::default(), - } + Ok(()) } - fn requiredVariables(&mut self) -> Option<&ExposedTypes> { - self.requiring.clear(); + fn cleanup(&mut self, ctx: Option<&Context>) -> Result<(), &str> { + for s in &mut self.header_shards { + if let Some(s) = s { + s.cleanup(ctx); + } + } + for s in &mut self.column_shards { + s.cleanup(ctx); + } - // Add UI.Parents to the list of required variables - util::require_parents(&mut self.requiring); + for s in &mut self.column_settings { + if let Some(s) = s { + s.cleanup(ctx); + } + } - Some(&self.requiring) + self.cleanup_helper(ctx)?; + Ok(()) } - fn exposedVariables(&mut self) -> Option<&ExposedTypes> { - self.exposing.clear(); + fn compose(&mut self, data: &InstanceData) -> Result { + self.compose_helper(data)?; - let mut exposed = false; - for s in &self.shards { - exposed |= util::expose_contents_variables(&mut self.exposing, s); - } + TableContext::enter_table_compose(); + + self.column_shards.clear(); + self.header_shards.clear(); - if exposed { - Some(&self.exposing) + self.remap_key_seq = data.inputType.basicType == SHTYPE_SEQ; + let callback_type = if self.remap_key_seq { + unsafe { + if data.inputType.details.seqTypes.len != 1 { + return Err("Table requires a sequence of one type"); + } + *data.inputType.details.seqTypes.elements + } } else { - None + common_type::int + }; + + // Compose interaction callbacks with int input type + let callback_data = InstanceData { + inputType: callback_type, + ..*data + }; + + let header_data = InstanceData { + inputType: common_type::int, + ..*data + }; + + // Process columns + if let Ok(columns) = Seq::try_from(&self.columns.0) { + // Each column entry is a shards sequence directly + for column in columns.iter() { + // Each column should be a sequence of shards + let mut column_shard = ShardsVar::default(); + column_shard.set_param(&column)?; + + // Compose the column shards - this will trigger any UI.Header inside + column_shard.compose(&callback_data)?; + shards::util::require_shards_contents(&mut self.requiring, &mut column_shard); + + // Store the header if one was registered during compose + let (header, column_settings) = TableContext::take_header(); + self.header_shards.push(header); + self.column_shards.push(column_shard); + + self.column_settings.push(column_settings); + } } - } - fn hasCompose() -> bool { - true - } + // Compose the column settings + for settings in &mut self.column_settings { + if let Some(settings) = settings { + settings.compose(data, &mut self.requiring)?; + } + } - fn compose(&mut self, data: &InstanceData) -> Result { - // validate + TableContext::exit_table_compose(); - if !self.columns.0.is_none() { - let columns = Seq::try_from(&self.columns.0)?; - let rows = Seq::try_from(&self.rows.0)?; - if columns.len() != rows.len() { - return Err("Columns and Builder parameters must have the same length."); + // Need to compose the collected headers + for hdr in &mut self.header_shards { + if let Some(hdr) = hdr { + hdr.compose(&header_data)?; + shards::util::require_shards_contents(&mut self.requiring, hdr); } } - // compose header shards - for header_shard in &mut self.header_shards { - if let Some(header_shard) = header_shard { - header_shard.compose(data)?; + // Expose variables from column shards + self.inner_exposed.clear(); + for shard in &self.column_shards { + if let Some(exposed) = shard.get_exposing() { + self.inner_exposed.extend_from_slice(exposed); } } - // compose row shards - let mut data = *data; - let mut shared: ExposedTypes; - - // we need to inject the row index to the inner shards - if self.row_index.is_variable() { - // clone shared into a new vector we can append things to - shared = data.shared.into(); - let mut should_expose = true; - for var in &shared { - let (a, b) = unsafe { - ( - CStr::from_ptr(var.name), - CStr::from_ptr(self.row_index.get_name()), - ) - }; - if CStr::cmp(a, b) == Ordering::Equal { - should_expose = false; - if var.exposedType.basicType != SHType_Int { - return Err("Table: int variable required."); - } - break; - } + // Required variables from column shards + self.requiring.clear(); + util::require_parents(&mut self.requiring); + for shard in &self.column_shards { + if let Some(required) = shard.get_requiring() { + self.requiring.extend_from_slice(required); } + } - if should_expose { - // expose row index - let index_info = ExposedInfo { - exposedType: common_type::int, - name: self.row_index.get_name(), - help: shccstr!("The row index."), - isMutable: false, - isProtected: false, - global: false, - tracked: false, - declared: true, - }; - shared.push(index_info); - // update shared - data.shared = (&shared).into(); + // Compose IsSelected callback + if !self.is_selected_callback.is_empty() { + let cr = self.is_selected_callback.compose(&callback_data)?; + if cr.outputType != common_type::bool { + return Err("IsSelected should return a boolean"); } + shards::util::require_shards_contents(&mut self.requiring, &self.is_selected_callback); } - let input_type = data.inputType; - let slice = unsafe { - let ptr = input_type.details.seqTypes.elements; - from_raw_parts_allow_null(ptr, input_type.details.seqTypes.len as usize) + // Compose other callbacks + self.clicked_callback.compose(&callback_data)?; + shards::util::require_shards_contents(&mut self.requiring, &self.clicked_callback); + + self.double_clicked_callback.compose(&callback_data)?; + shards::util::require_shards_contents(&mut self.requiring, &self.double_clicked_callback); + + self.context_menu.compose(&callback_data)?; + shards::util::require_shards_contents(&mut self.requiring, &self.context_menu); + + self.can_interact = !self.is_selected_callback.is_empty() + || !self.clicked_callback.is_empty() + || !self.double_clicked_callback.is_empty() + || !self.context_menu.is_empty(); + + Ok(data.inputType) + } + + fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { + let ui = util::get_parent_ui(self.parents.get())?; + ui.push_id(EguiId::new(self, 0), |ui| { + self.build_table(context, input, ui) + }) + .inner?; + + Ok(Some(input.clone())) + } +} + +impl Table { + fn build_table(&mut self, context: &Context, input: &Var, ui: &mut egui::Ui) -> Result<(), &str> { + use egui_extras::{Column, TableBuilder}; + + // Get row count from input + let row_count: i64 = if self.remap_key_seq { + input.as_seq().unwrap().len() as i64 + } else { + input.try_into()? }; - let element_type = if !slice.is_empty() { - slice[0] + + // Get row height - default to text height if not specified + let text_height = egui::TextStyle::Body.resolve(ui.style()).size; + let row_height = if !self.row_height.get().is_none() { + self.row_height.get().try_into()? } else { - common_type::any + text_height }; - for s in &mut self.shards { - data.inputType = element_type; - s.compose(&data)?; - } + let _root_id = ui.id(); - // Always passthrough the input - Ok(input_type) - } + // Start building table + let mut builder = + TableBuilder::new(ui).cell_layout(egui::Layout::left_to_right(egui::Align::Center)); - fn warmup(&mut self, ctx: &Context) -> Result<(), &str> { - self.parents.warmup(ctx); + let striped = self.striped.get(); + builder = builder.striped(striped.try_into()?); - self.striped.warmup(ctx); - self.resizable.warmup(ctx); - self.row_index.warmup(ctx); + let resizable = self.resizable.get(); + builder = builder.resizable(resizable.try_into()?); - if self.row_index.is_variable() { - self.row_index.get_mut().valueType = common_type::int.basicType; + if self.can_interact { + builder = builder.sense(egui::Sense::click()); } - for s in &mut self.shards { - s.warmup(ctx)?; - } - for s in &mut self.header_shards { - if let Some(s) = s { - s.warmup(ctx)?; - } - } + // Configure columns + for i in 0..self.column_shards.len() { + // Create column based on settings + let column = if let Some(settings) = &self.column_settings[i] { + // Configure column based on settings + let mut col = unsafe { + if let Ok(col) = settings.width_type.get().try_into() { + match col { + "auto" => { + if !settings.width_value.get().is_none() { + Column::auto_with_initial_suggestion(settings.width_value.get().try_into().unwrap_unchecked()) + } else { + Column::auto() + } + } + "initial" => { + if !settings.width_value.get().is_none() { + Column::initial(settings.width_value.get().try_into().unwrap_unchecked()) + } else { + Column::initial(100.0) // Default width + } + } + "exact" => { + if !settings.width_value.get().is_none() { + Column::exact(settings.width_value.get().try_into().unwrap_unchecked()) + } else { + Column::exact(100.0) // Default width + } + } + _ => Column::remainder(), + } + } else { + Column::remainder() + } + }; - Ok(()) - } + unsafe { + // Apply additional settings + if !settings.min_width.get().is_none() { + col = col.at_least(settings.min_width.get().try_into().unwrap_unchecked()); + } - fn cleanup(&mut self, ctx: Option<&Context>) -> Result<(), &str> { - for s in &mut self.header_shards { - if let Some(s) = s { - s.cleanup(ctx); - } - } - for s in &mut self.shards { - s.cleanup(ctx); + if !settings.max_width.get().is_none() { + col = col.at_most(settings.max_width.get().try_into().unwrap_unchecked()); + } + + if !settings.clip.get().is_none() { + col = col.clip(settings.clip.get().try_into().unwrap_unchecked()); + } + + if !settings.resizable.get().is_none() { + col = col.resizable(settings.resizable.get().try_into().unwrap_unchecked()); + } + } + + col + } else { + // Default column type + Column::remainder() + }; + + builder = builder.column(column); } - self.row_index.cleanup(ctx); - self.resizable.cleanup(ctx); - self.striped.cleanup(ctx); + // Build table with headers and content + let table = builder.header(20.0, |mut header_row| { + // Render headers + for (i, header) in self.header_shards.iter_mut().enumerate() { + let input = Var::new_int(i as i64); + header_row.col(|ui| { + if let Some(header) = header { + let _ = util::activate_ui_contents(context, &input, ui, &mut self.parents, header); + } + }); + } + }); + + // Populate rows + let reverse: bool = self.reversed.get().try_into()?; + table.body(|body: egui_extras::TableBody<'_>| { + body.rows(row_height, row_count as usize, |mut row| { + let index = if reverse { + row_count - 1 - row.index() as i64 + } else { + row.index() as i64 + }; + let idx_var: Var = if self.remap_key_seq { + input.as_seq().unwrap()[index as usize] + } else { + Var::new_int(index) + }; - self.parents.cleanup(ctx); - Ok(()) - } + // Check if row is selected + let mut is_selected = false; + if !self.is_selected_callback.is_empty() { + let mut is_selected_var = Var::default(); + if self + .is_selected_callback + .activate(context, &idx_var, &mut is_selected_var) + == WireState::Error + { + return; + } + is_selected = (&is_selected_var).try_into().unwrap_or(false); + } - fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { - if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { - ui.push_id(EguiId::new(self, 0), |ui| { - use egui_extras::{Column, TableBuilder}; + // Style selected rows + row.set_selected(is_selected); - let seq = Seq::try_from(input)?; + // Render columns + for column in &mut self.column_shards { + row.col(|ui| { + let _ = util::activate_ui_contents(context, &idx_var, ui, &mut self.parents, column); + }); + } - let text_height = egui::TextStyle::Body.resolve(ui.style()).size; + // Create row response for interaction + let row_response = row.response(); + + // Handle interactions + if !self.context_menu.is_empty() { + row_response.context_menu(|ui| { + let _ = util::activate_ui_contents( + context, + &idx_var, + ui, + &mut self.parents, + &mut self.context_menu, + ); + }); + } - // start building a table - let mut builder = - TableBuilder::new(ui).cell_layout(egui::Layout::left_to_right(egui::Align::Center)); - let striped = self.striped.get(); - if !striped.is_none() { - builder = builder.striped(striped.try_into()?); + if row_response.double_clicked() { + let mut _unused = Var::default(); + let _ = self + .double_clicked_callback + .activate(context, &idx_var, &mut _unused); } - let resizable = self.resizable.get(); - if !resizable.is_none() { - builder = builder.resizable(resizable.try_into()?); + + if row_response.clicked() { + let mut _unused = Var::default(); + let _ = self + .clicked_callback + .activate(context, &idx_var, &mut _unused); } + }); + }); - // configure columns - let table = { - let columns: Seq = self.columns.0.as_ref().try_into().unwrap_or_default(); - - for column in columns.iter() { - let column: shards::types::Table = column.as_ref().try_into()?; - let mut size = if let Some(initial) = column.get_static("Initial") { - Column::initial(initial.try_into()?) - } else { - Column::remainder() - }; - if let Some(at_least) = column.get_static("AtLeast") { - size = size.at_least(at_least.try_into()?); - } - if let Some(at_most) = column.get_static("AtMost") { - size = size.at_most(at_most.try_into()?); - } - builder = builder.column(size); - } + Ok(()) + } +} - // add columns - for _ in columns.len()..self.shards.len() { - builder = builder.column(Column::remainder()); - } +#[derive(shards::shard)] +#[shard_info("UI.TableHeader", "Defines a header for a Table column.")] +pub struct Header { + #[shard_param("Contents", "The UI contents for the header.", SEQ_OF_SHARDS_TYPES)] + contents: ShardsVar, + + #[shard_param( + "WidthType", + "Column width type: auto, initial, exact, or remainder.", + STRING_VAR_OR_NONE_SLICE + )] + width_type: ParamVar, + + #[shard_param( + "Width", + "Width value for initial or exact width types.", + FLOAT_VAR_OR_NONE_SLICE + )] + width_value: ParamVar, + + #[shard_param("MinWidth", "Minimum width of the column.", FLOAT_VAR_OR_NONE_SLICE)] + min_width: ParamVar, + + #[shard_param("MaxWidth", "Maximum width of the column.", FLOAT_VAR_OR_NONE_SLICE)] + max_width: ParamVar, + + #[shard_param( + "Clip", + "Whether to clip content that doesn't fit in the column.", + BOOL_VAR_OR_NONE_SLICE + )] + clip: ParamVar, + + #[shard_param( + "Resizable", + "Whether this column can be resized.", + BOOL_VAR_OR_NONE_SLICE + )] + resizable: ParamVar, +} - // column headers - let row_height = if columns.len() > 0 { 20.0 } else { 0.0 }; - builder.header(row_height, |mut header_row| { - for i in 0..columns.len() { - let column: shards::types::Table = columns[i].as_ref().try_into().unwrap(); // iterated successfully above, qed - if let Some(header) = column.get_static("Header") { - match header.valueType { - SHType_String => { - header_row.col(|ui| { - let text: &str = header.try_into().unwrap_or(""); - ui.heading(text); - }); - } - SHType_ShardRef | SHType_Seq => { - if let Some(header_shard) = &mut self.header_shards[i] { - header_row.col(|ui| { - let _ = util::activate_ui_contents( - context, - input, - ui, - &mut self.parents, - header_shard, - ); - }); - } else { - header_row.col(|_| {}); - } - } - _ => unreachable!(), - } - } else { - header_row.col(|_| {}); - } - } - }) - }; +impl Default for Header { + fn default() -> Self { + Self { + contents: ShardsVar::default(), + width_type: ParamVar::default(), + width_value: ParamVar::default(), + min_width: ParamVar::default(), + max_width: ParamVar::default(), + clip: ParamVar::default(), + resizable: ParamVar::default(), + } + } +} - // populate rows - table.body(|body| { - body.rows(text_height, seq.len(), |mut row| { - let index = row.index(); - if self.row_index.is_variable() { - let var: &mut i64 = self.row_index.get_mut().try_into().unwrap(); // row_index is set int during warmup, qed - *var = index as i64; - } - for s in &mut self.shards { - row.col(|ui| { - let _ = util::activate_ui_contents(context, &seq[index], ui, &mut self.parents, s); - }); - } - }) - }); +#[shards::shard_impl] +impl Shard for Header { + fn input_types(&mut self) -> &Types { + &ANY_TYPES + } - Ok::<(), &str>(()) - }) - .inner?; + fn output_types(&mut self) -> &Types { + &ANY_TYPES + } - // Always passthrough the input - Ok(None) + fn compose(&mut self, data: &InstanceData) -> Result { + if !TableContext::is_inside_table_compose() { + return Err("UI.Header can only be used within UI.Table columns"); + } + + // Create column settings from parameters + + let have_settings = !self.width_type.is_none() + || !self.width_value.is_none() + || !self.min_width.is_none() + || !self.max_width.is_none() + || !self.clip.is_none() + || !self.resizable.is_none(); + let settings = if have_settings { + Some(ColumnSettings { + width_type: ParamVar::new(self.width_type.parameter.0), + width_value: ParamVar::new(self.width_value.parameter.0), + min_width: ParamVar::new(self.min_width.parameter.0), + max_width: ParamVar::new(self.max_width.parameter.0), + clip: ParamVar::new(self.clip.parameter.0), + resizable: ParamVar::new(self.resizable.parameter.0), + }) } else { - Err("No UI parent") + None + }; + + // Store header contents and settings in context + TableContext::set_header(self.contents.clone(), settings)?; + + unsafe { + // TODO: Make external function for this + (*data.shard).inlineShardId = 1; // Make a noop shard } + + // Passthrough input type + Ok(data.inputType) + } + + fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + // Passthrough during activation (will be nooped in actual use) + Ok(Some(input.clone())) } } + +pub(crate) fn register_shards() { + register_shard::
(); + register_shard::
(); +} diff --git a/shards/egui/src/menus/menu.rs b/shards/egui/src/menus/menu.rs index 27d10b9b67..8faa26cf69 100644 --- a/shards/egui/src/menus/menu.rs +++ b/shards/egui/src/menus/menu.rs @@ -117,7 +117,7 @@ impl LegacyShard for CloseMenu { Ok(()) } - fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, _context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { ui.close_menu(); Ok(None) diff --git a/shards/egui/src/misc/debug.rs b/shards/egui/src/misc/debug.rs index a2d443ae27..33422143e7 100644 --- a/shards/egui/src/misc/debug.rs +++ b/shards/egui/src/misc/debug.rs @@ -1,7 +1,7 @@ -use egui::CentralPanel; -use shards::core::{register_enum, register_shard}; + +use shards::core::{register_shard}; use shards::shard::Shard; -use shards::types::{common_type, ANY_TYPES}; +use shards::types::{ANY_TYPES}; use shards::types::{Context, ExposedTypes, InstanceData, ParamVar, Type, Types, Var}; use crate::{util, CONTEXTS_NAME, PARENTS_UI_NAME}; diff --git a/shards/egui/src/misc/mod.rs b/shards/egui/src/misc/mod.rs index ad2f43ce91..77a9943919 100644 --- a/shards/egui/src/misc/mod.rs +++ b/shards/egui/src/misc/mod.rs @@ -20,7 +20,7 @@ mod reset; mod style; mod painter; mod debug; -pub(crate) mod style_util; +pub mod style_util; pub fn register_shards() { register_legacy_shard::(); diff --git a/shards/egui/src/misc/painter.rs b/shards/egui/src/misc/painter.rs index c99a48a855..b5836e37d0 100644 --- a/shards/egui/src/misc/painter.rs +++ b/shards/egui/src/misc/painter.rs @@ -1,16 +1,16 @@ use crate::util::{self, try_into_color}; -use egui::{Color32, LayerId, Pos2, Rect, Rgba, Rounding, Stroke, Ui, UiStackInfo, Vec2}; +use egui::{LayerId, Pos2, Rect, Rgba, Stroke, Ui, UiStackInfo, Vec2}; use shards::core::register_shard; use shards::types::{ common_type, Context, ExposedTypes, InstanceData, OptionalString, ParamVar, ShardsVar, Type, - Types, Var, ANY_TYPES, FLOAT_TYPES, SHARDS_OR_NONE_TYPES, + Types, Var, ANY_TYPES, SHARDS_OR_NONE_TYPES, }; use crate::shards::shard; use crate::shards::shard::Shard; use crate::{EguiId, Order, CONTEXTS_NAME, HELP_VALUE_IGNORED, PARENTS_UI_NAME}; -use shards::types::COLOR_TYPES; -use shards::types::FLOAT2_TYPES; + + lazy_static! { pub static ref COLOR_VAR_TYPES: Types = vec![common_type::color, common_type::color_var]; @@ -105,7 +105,7 @@ impl Shard for CanvasShard { &mut ui, &mut self.parents, &mut self.contents, - ); + )?; Ok(None) } diff --git a/shards/egui/src/misc/reset.rs b/shards/egui/src/misc/reset.rs index ddcef2d9cd..68e639a8c2 100644 --- a/shards/egui/src/misc/reset.rs +++ b/shards/egui/src/misc/reset.rs @@ -86,7 +86,7 @@ impl LegacyShard for Reset { Ok(()) } - fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, _context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { ui.ctx().memory_mut(|mem| *mem = Default::default()); diff --git a/shards/egui/src/misc/style.rs b/shards/egui/src/misc/style.rs index 33c6812b2b..af7cdcd0a7 100644 --- a/shards/egui/src/misc/style.rs +++ b/shards/egui/src/misc/style.rs @@ -616,7 +616,7 @@ impl Shard for StyleShard { // Visuals stuff let visuals = &mut style.visuals; - when_set(&self.dark_mode, |v| Ok(visuals.dark_mode = dark_mode))?; + when_set(&self.dark_mode, |v| Ok(visuals.dark_mode = v.try_into()?))?; when_set(&self.override_text_color, |v| { Ok(visuals.override_text_color = Some(into_color(v)?)) diff --git a/shards/egui/src/misc/style_util.rs b/shards/egui/src/misc/style_util.rs index d80fe544c7..38a0fdec90 100644 --- a/shards/egui/src/misc/style_util.rs +++ b/shards/egui/src/misc/style_util.rs @@ -4,13 +4,13 @@ use shards::shardsc::SHColor; use shards::types::Table; -pub(crate) fn get_color(color: SHColor) -> Result { +pub fn get_color(color: SHColor) -> Result { Ok(egui::Color32::from_rgba_unmultiplied( color.r, color.g, color.b, color.a, )) } -pub(crate) fn get_font_family(family: &str) -> Result { +pub fn get_font_family(family: &str) -> Result { match family { "Proportional" => Ok(egui::FontFamily::Proportional), "Monospace" => Ok(egui::FontFamily::Monospace), @@ -20,7 +20,7 @@ pub(crate) fn get_font_family(family: &str) -> Result Result { +pub fn get_font_id(font_id: Table) -> Result { if let (Some(size), Some(family)) = (font_id.get_static("size"), font_id.get_static("family")) { let size: f32 = size.try_into().map_err(|e| { shlog!("{}: {}", "size", e); @@ -39,7 +39,7 @@ pub(crate) fn get_font_id(font_id: Table) -> Result } } -pub(crate) fn get_margin( +pub fn get_margin( margin: (f32, f32, f32, f32), ) -> Result { Ok(egui::Margin { @@ -50,7 +50,7 @@ pub(crate) fn get_margin( }) } -pub(crate) fn get_rounding(rounding: (f32, f32, f32, f32)) -> Result { +pub fn get_rounding(rounding: (f32, f32, f32, f32)) -> Result { Ok(egui::Rounding { nw: rounding.0, ne: rounding.1, @@ -59,7 +59,7 @@ pub(crate) fn get_rounding(rounding: (f32, f32, f32, f32)) -> Result Result { +pub fn get_shadow(shadow: Table) -> Result { if let (Some(extrusion), Some(color)) = (shadow.get_static("extrusion"), shadow.get_static("color")) { @@ -80,7 +80,7 @@ pub(crate) fn get_shadow(shadow: Table) -> Result Result { +pub fn get_stroke(stroke: Table) -> Result { if let (Some(width), Some(color)) = (stroke.get_static("width"), stroke.get_static("color")) { Ok(egui::Stroke { width: width.try_into().map_err(|e| { @@ -97,13 +97,13 @@ pub(crate) fn get_stroke(stroke: Table) -> Result { } } -pub(crate) fn get_text_format(format: Table) -> Result { +pub fn get_text_format(format: Table) -> Result { let mut text_format = egui::TextFormat::default(); update_text_format(&mut text_format, format)?; Ok(text_format) } -pub(crate) fn get_text_style(name: &str) -> Result { +pub fn get_text_style(name: &str) -> Result { match name { "Small" => Ok(egui::TextStyle::Small), "Body" => Ok(egui::TextStyle::Body), @@ -115,7 +115,7 @@ pub(crate) fn get_text_style(name: &str) -> Result Result<(), &'static str> { diff --git a/shards/egui/src/state.rs b/shards/egui/src/state.rs index dc13c11ed9..ec2123923b 100644 --- a/shards/egui/src/state.rs +++ b/shards/egui/src/state.rs @@ -1,4 +1,4 @@ -use lazy_static::__Deref; + use crate::util; use crate::CONTEXTS_NAME; diff --git a/shards/egui/src/util.rs b/shards/egui/src/util.rs index 134b6cc378..ebe2f37fe5 100644 --- a/shards/egui/src/util.rs +++ b/shards/egui/src/util.rs @@ -240,8 +240,6 @@ pub fn into_rounding(v: &Var) -> Result { pub fn into_shadow(v: &Var, ctx: &Context) -> Result { let tbl: TableVar = v.try_into()?; - let spread: f32 = - get_or_var(tbl.get_static("Spread").ok_or("Spread missing")?, ctx).try_into()?; let blur: f32 = get_or_var(tbl.get_static("Blur").ok_or("Blur missing")?, ctx).try_into()?; let spread: f32 = get_or_var(tbl.get_static("Spread").ok_or("Spread missing")?, ctx).try_into()?; diff --git a/shards/egui/src/visual/mod.rs b/shards/egui/src/visual/mod.rs index cfb5fbef5b..b3d876727c 100644 --- a/shards/egui/src/visual/mod.rs +++ b/shards/egui/src/visual/mod.rs @@ -1,5 +1,6 @@ // prevent upper case globals #![allow(non_upper_case_globals)] +#![allow(dead_code)] use directory::{get_global_map, get_global_name_btree}; use egui::*; @@ -1422,22 +1423,22 @@ impl<'a> AstMutator> for VisualAst<'a> { fn visit_assignment(&mut self, assignment: &mut Assignment) -> Option { let resp = match assignment.kind { - AssignmentKind::AssignRef => ({ + AssignmentKind::AssignRef => { self.ui.label(chars("=")); assignment.identifier.accept_mut(self) - }), - AssignmentKind::AssignSet => ( { + }, + AssignmentKind::AssignSet => { self.ui.label(chars(">=")); assignment.identifier.accept_mut(self) - }), - AssignmentKind::AssignUpd => ( { + }, + AssignmentKind::AssignUpd => { self.ui.label(chars(">")); assignment.identifier.accept_mut(self) - }), - AssignmentKind::AssignPush => ( { + }, + AssignmentKind::AssignPush => { self.ui.label(chars(">>")); assignment.identifier.accept_mut(self) - }), + }, }; resp.map(|x| { if x.clicked() { diff --git a/shards/egui/src/widgets/code_editor/syntax_highlighting.rs b/shards/egui/src/widgets/code_editor/syntax_highlighting.rs index dd947f504e..167d2daf8e 100644 --- a/shards/egui/src/widgets/code_editor/syntax_highlighting.rs +++ b/shards/egui/src/widgets/code_editor/syntax_highlighting.rs @@ -97,7 +97,6 @@ impl CodeTheme { struct Highlighter { syntaxes: SyntaxSet, - themes: ThemeSet, } impl Default for Highlighter { @@ -118,7 +117,6 @@ impl Default for Highlighter { Highlighter { syntaxes, - themes: ThemeSet::load_defaults(), } } } diff --git a/shards/egui/src/widgets/console.rs b/shards/egui/src/widgets/console.rs index a52cc3e33f..91f731c710 100644 --- a/shards/egui/src/widgets/console.rs +++ b/shards/egui/src/widgets/console.rs @@ -10,7 +10,7 @@ use crate::PARENTS_UI_NAME; use shards::shard::LegacyShard; use shards::types::Context; -use shards::types::ExposedInfo; + use shards::types::ExposedTypes; use shards::types::OptionalString; use shards::types::ParamVar; @@ -219,20 +219,6 @@ impl Console { code: &str, filters: (bool, bool, bool, bool, bool), ) -> egui::text::LayoutJob { - impl - egui::util::cache::ComputerMut< - (&LogTheme, &str, (bool, bool, bool, bool, bool)), - egui::text::LayoutJob, - > for Highlighter - { - fn compute( - &mut self, - (theme, code, filters): (&LogTheme, &str, (bool, bool, bool, bool, bool)), - ) -> egui::text::LayoutJob { - self.highlight(theme, code, filters) - } - } - type HighlightCache = egui::util::cache::FrameCache; ctx.memory_mut(|mem| { @@ -245,6 +231,20 @@ impl Console { #[derive(Default)] struct Highlighter {} +impl + egui::util::cache::ComputerMut< + (&LogTheme, &str, (bool, bool, bool, bool, bool)), + egui::text::LayoutJob, + > for Highlighter +{ + fn compute( + &mut self, + (theme, code, filters): (&LogTheme, &str, (bool, bool, bool, bool, bool)), + ) -> egui::text::LayoutJob { + self.highlight(theme, code, filters) + } +} + impl Highlighter { fn highlight( &self, diff --git a/shards/egui/src/widgets/hex_viewer.rs b/shards/egui/src/widgets/hex_viewer.rs index c4d8161492..3723091c45 100644 --- a/shards/egui/src/widgets/hex_viewer.rs +++ b/shards/egui/src/widgets/hex_viewer.rs @@ -6,19 +6,21 @@ use crate::util; use crate::HELP_OUTPUT_EQUAL_INPUT; use crate::PARENTS_UI_NAME; use shards::shard::LegacyShard; -use shards::shardsc::SHType_Bytes; -use shards::shardsc::SHType_Enum; -use shards::shardsc::SHType_Float; -use shards::shardsc::SHType_Float2; -use shards::shardsc::SHType_Float3; -use shards::shardsc::SHType_Float4; -use shards::shardsc::SHType_Int; -use shards::shardsc::SHType_Int16; -use shards::shardsc::SHType_Int2; -use shards::shardsc::SHType_Int3; -use shards::shardsc::SHType_Int4; -use shards::shardsc::SHType_Int8; -use shards::shardsc::SHType_String; +use shards::shardsc::{ + SHType_Bytes as SHTYPE_BYTES, + SHType_Enum as SHTYPE_ENUM, + SHType_Float as SHTYPE_FLOAT, + SHType_Float2 as SHTYPE_FLOAT2, + SHType_Float3 as SHTYPE_FLOAT3, + SHType_Float4 as SHTYPE_FLOAT4, + SHType_Int as SHTYPE_INT, + SHType_Int16 as SHTYPE_INT16, + SHType_Int2 as SHTYPE_INT2, + SHType_Int3 as SHTYPE_INT3, + SHType_Int4 as SHTYPE_INT4, + SHType_Int8 as SHTYPE_INT8, + SHType_String as SHTYPE_STRING +}; use shards::types::common_type; use shards::types::Context; use shards::types::ExposedTypes; @@ -135,55 +137,55 @@ impl LegacyShard for HexViewer { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { let mem = unsafe { let (data, len) = match input.valueType { - SHType_Bytes => ( + SHTYPE_BYTES => ( input.payload.__bindgen_anon_1.__bindgen_anon_4.bytesValue, input.payload.__bindgen_anon_1.__bindgen_anon_4.bytesSize as usize, ), - SHType_Enum => ( + SHTYPE_ENUM => ( &input.payload.__bindgen_anon_1.__bindgen_anon_3.enumValue as *const i32 as *mut u8, 4, ), - SHType_Float => ( + SHTYPE_FLOAT => ( &input.payload.__bindgen_anon_1.floatValue as *const f64 as *mut u8, 8, ), - SHType_Float2 => ( + SHTYPE_FLOAT2 => ( &input.payload.__bindgen_anon_1.float2Value as *const f64 as *mut u8, 16, ), - SHType_Float3 => ( + SHTYPE_FLOAT3 => ( &input.payload.__bindgen_anon_1.float3Value as *const f32 as *mut u8, 12, ), - SHType_Float4 => ( + SHTYPE_FLOAT4 => ( &input.payload.__bindgen_anon_1.float4Value as *const f32 as *mut u8, 16, ), - SHType_Int => ( + SHTYPE_INT => ( &input.payload.__bindgen_anon_1.intValue as *const i64 as *mut u8, 8, ), - SHType_Int2 => ( + SHTYPE_INT2 => ( &input.payload.__bindgen_anon_1.int2Value as *const i64 as *mut u8, 16, ), - SHType_Int3 => ( + SHTYPE_INT3 => ( &input.payload.__bindgen_anon_1.int3Value as *const i32 as *mut u8, 12, ), - SHType_Int4 => ( + SHTYPE_INT4 => ( &input.payload.__bindgen_anon_1.int4Value as *const i32 as *mut u8, 16, ), - SHType_Int8 => ( + SHTYPE_INT8 => ( &input.payload.__bindgen_anon_1.int8Value as *const i16 as *mut u8, 16, ), - SHType_Int16 => ( + SHTYPE_INT16 => ( &input.payload.__bindgen_anon_1.int16Value as *const i8 as *mut u8, 16, ), - SHType_String => ( + SHTYPE_STRING => ( input.payload.__bindgen_anon_1.__bindgen_anon_2.stringValue as *mut u8, input.payload.__bindgen_anon_1.__bindgen_anon_2.stringLen as usize, ), diff --git a/shards/egui/src/widgets/image.rs b/shards/egui/src/widgets/image.rs index afc3c7f59a..81f780e15f 100644 --- a/shards/egui/src/widgets/image.rs +++ b/shards/egui/src/widgets/image.rs @@ -10,8 +10,10 @@ use crate::PARENTS_UI_NAME; use egui::load::SizedTexture; use shards::core::register_shard; use shards::shard::Shard; -use shards::shardsc::SHType_Image; -use shards::shardsc::SHType_Object; +use shards::shardsc::{ + SHType_Image as SHTYPE_IMAGE, + SHType_Object as SHTYPE_OBJECT +}; use shards::types::Context; use shards::types::ExposedTypes; use shards::types::InstanceData; @@ -81,10 +83,10 @@ impl Shard for Image { util::require_parents(&mut self.requiring); match data.inputType.basicType { - SHType_Image => decl_override_activate! { + SHTYPE_IMAGE => decl_override_activate! { data.activate = Image::activate_image_override; }, - SHType_Object + SHTYPE_OBJECT if unsafe { data.inputType.details.object.typeId } == image_util::TEXTURE_CC => { decl_override_activate! { diff --git a/shards/egui/src/widgets/image_button.rs b/shards/egui/src/widgets/image_button.rs index 3a226813fa..dafcae3fc1 100644 --- a/shards/egui/src/widgets/image_button.rs +++ b/shards/egui/src/widgets/image_button.rs @@ -9,9 +9,11 @@ use crate::PARENTS_UI_NAME; use egui::load::SizedTexture; use shards::core::register_shard; use shards::shard::Shard; -use shards::shardsc::SHType_Bool; -use shards::shardsc::SHType_Image; -use shards::shardsc::SHType_Object; +use shards::shardsc::{ + SHType_Bool as SHTYPE_BOOL, + SHType_Image as SHTYPE_IMAGE, + SHType_Object as SHTYPE_OBJECT, +}; use shards::types::common_type; use shards::types::Context; use shards::types::ExposedInfo; @@ -141,7 +143,7 @@ impl Shard for ImageButton { }; if CStr::cmp(a, b) == Ordering::Equal { self.should_expose = false; - if var.exposedType.basicType != SHType_Bool { + if var.exposedType.basicType != SHTYPE_BOOL { return Err("ImageButton: bool variable required."); } break; @@ -154,10 +156,10 @@ impl Shard for ImageButton { } match data.inputType.basicType { - SHType_Image => decl_override_activate! { + SHTYPE_IMAGE => decl_override_activate! { data.activate = ImageButton::image_activate; }, - SHType_Object + SHTYPE_OBJECT if unsafe { data.inputType.details.object.typeId } == image_util::TEXTURE_CC => { decl_override_activate! { diff --git a/shards/egui/src/widgets/label.rs b/shards/egui/src/widgets/label.rs index 3ecb90f551..88cc449544 100644 --- a/shards/egui/src/widgets/label.rs +++ b/shards/egui/src/widgets/label.rs @@ -3,6 +3,7 @@ use crate::util; use crate::widgets::text_util; use crate::widgets::TEXTWRAP_TYPE; use crate::ANY_TABLE_OR_NONE_SLICE; +use crate::BOOL_VAR_SLICE; use crate::PARENTS_UI_NAME; use egui::TextWrapMode; use shards::shard::Shard; @@ -31,6 +32,13 @@ pub(crate) struct Label { #[shard_param("Style", "The text style.", ANY_TABLE_OR_NONE_SLICE)] style: ClonedVar, + + #[shard_param( + "Selectable", + "Whether the text can be selected.", + BOOL_VAR_SLICE + )] + selectable: ParamVar, } impl Default for Label { @@ -40,6 +48,7 @@ impl Default for Label { parents: ParamVar::new_named(PARENTS_UI_NAME), wrap: ClonedVar(TextWrap::Extend.into()), style: ClonedVar::default(), + selectable: ParamVar::new(Var::new_bool(true)), } } } @@ -82,6 +91,9 @@ impl Shard for Label { let mut label = egui::Label::new(text); + let selectable = self.selectable.get().try_into()?; + label = label.selectable(selectable); + let wrap = &self.wrap.0; if wrap.is_bool() { let wrap: bool = wrap.try_into()?; diff --git a/shards/egui/src/widgets/mod.rs b/shards/egui/src/widgets/mod.rs index 38b890172a..96f3570115 100644 --- a/shards/egui/src/widgets/mod.rs +++ b/shards/egui/src/widgets/mod.rs @@ -2,7 +2,7 @@ /* Copyright © 2022 Fragcolor Pte. Ltd. */ use egui_memory_editor::MemoryEditor; -use image_util::AutoTexturePtr; + use shards::core::register_enum; use shards::core::register_legacy_shard; use shards::core::register_shard; @@ -99,14 +99,6 @@ struct RenderTarget { scale: ParamVar, } -/// Displays text. -struct Label { - parents: ParamVar, - requiring: ExposedTypes, - wrap: ParamVar, - style: ParamVar, -} - struct Link { parents: ParamVar, requiring: ExposedTypes, @@ -173,11 +165,6 @@ struct Variable { inner_type: Option, } -struct WireVariable { - parents: ParamVar, - requiring: ExposedTypes, -} - macro_rules! decl_ui_input { ($name:ident, $tmp_type:ty) => { struct $name { diff --git a/shards/egui/src/widgets/spinner.rs b/shards/egui/src/widgets/spinner.rs index 2974ab084a..31cd635f8f 100644 --- a/shards/egui/src/widgets/spinner.rs +++ b/shards/egui/src/widgets/spinner.rs @@ -122,7 +122,7 @@ impl LegacyShard for Spinner { Ok(()) } - fn activate(&mut self, _context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, _context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { let mut spinner = egui::Spinner::new(); diff --git a/shards/egui/src/widgets/var_util.rs b/shards/egui/src/widgets/var_util.rs index 8b3cfcdbde..b2a7a6e57d 100644 --- a/shards/egui/src/widgets/var_util.rs +++ b/shards/egui/src/widgets/var_util.rs @@ -4,7 +4,29 @@ use crate::MutVarTextBuffer; use crate::UIRenderer; use egui::*; -use shards::shardsc::*; +use shards::shardsc::{ + // Import the type + SHType, + SHType_Bool as SHTYPE_BOOL, + SHType_Color as SHTYPE_COLOR, + SHType_Enum as SHTYPE_ENUM, + SHType_Float as SHTYPE_FLOAT, + SHType_Float2 as SHTYPE_FLOAT2, + SHType_Float3 as SHTYPE_FLOAT3, + SHType_Float4 as SHTYPE_FLOAT4, + SHType_Int as SHTYPE_INT, + SHType_Int16 as SHTYPE_INT16, + SHType_Int2 as SHTYPE_INT2, + SHType_Int3 as SHTYPE_INT3, + SHType_Int4 as SHTYPE_INT4, + SHType_Int8 as SHTYPE_INT8, + // Import with aliases + SHType_None as SHTYPE_NONE, + SHType_Path as SHTYPE_PATH, + SHType_Seq as SHTYPE_SEQ, + SHType_String as SHTYPE_STRING, + SHType_Table as SHTYPE_TABLE, +}; use shards::types::Table; use shards::types::Type; @@ -15,23 +37,23 @@ use super::drag_value::CustomDragValue; fn get_default_value(value_type: SHType) -> Var { match value_type { - SHType_None => Var::default(), - SHType_Bool => false.into(), - SHType_Int => 0.into(), - SHType_Int2 => (0, 0).into(), - SHType_Int3 => (0, 0, 0).into(), - SHType_Int4 => (0, 0, 0, 0).into(), - SHType_Float => 0.0.into(), - SHType_Float2 => (0.0, 0.0).into(), - SHType_Float3 => (0.0, 0.0, 0.0).into(), - SHType_Float4 => (0.0, 0.0, 0.0, 0.0).into(), - SHType_String => Var::ephemeral_string(""), + SHTYPE_NONE => Var::default(), + SHTYPE_BOOL => false.into(), + SHTYPE_INT => 0.into(), + SHTYPE_INT2 => (0, 0).into(), + SHTYPE_INT3 => (0, 0, 0).into(), + SHTYPE_INT4 => (0, 0, 0, 0).into(), + SHTYPE_FLOAT => 0.0.into(), + SHTYPE_FLOAT2 => (0.0, 0.0).into(), + SHTYPE_FLOAT3 => (0.0, 0.0, 0.0).into(), + SHTYPE_FLOAT4 => (0.0, 0.0, 0.0, 0.0).into(), + SHTYPE_STRING => Var::ephemeral_string(""), _ => Var::default(), } } unsafe fn render_enum( - id: egui::Id, + _id: egui::Id, var: &mut Var, ui: &mut Ui, ) -> Result> { @@ -54,12 +76,6 @@ unsafe fn render_enum( let labels = enum_info.get_fast_static("labels").as_seq().unwrap(); let values = enum_info.get_fast_static("values").as_seq().unwrap(); let mut index: usize = values.iter().position(|x| x == enum_value.into()).unwrap(); - let name: &str = enum_info - .get_fast_static("name") - .as_ref() - .try_into() - .unwrap(); - let label: &str = labels[index].as_ref().try_into().unwrap(); let r = ComboBox::new(id, "").show_index(ui, &mut index, values.len(), |i| { let r: &str = (&labels[i]).try_into().unwrap(); @@ -82,20 +98,20 @@ impl UIRenderer for Var { inner_type: Option<&Type>, ui: &mut Ui, ) -> Response { - if read_only && !matches!(self.valueType, SHType_Seq | SHType_Table) { - ui.set_enabled(false); + if read_only && !matches!(self.valueType, SHTYPE_SEQ | SHTYPE_TABLE) { + ui.disable(); } unsafe { match self.valueType { - SHType_None => ui.label(""), - SHType_Enum => { + SHTYPE_NONE => ui.label(""), + SHTYPE_ENUM => { render_enum(id, self, ui).unwrap_or_else(|_| ui.label("")) } - SHType_Bool => ui.checkbox(&mut self.payload.__bindgen_anon_1.boolValue, ""), - SHType_Int => ui.add(CustomDragValue::new( + SHTYPE_BOOL => ui.checkbox(&mut self.payload.__bindgen_anon_1.boolValue, ""), + SHTYPE_INT => ui.add(CustomDragValue::new( &mut self.payload.__bindgen_anon_1.intValue, )), - SHType_Int2 => { + SHTYPE_INT2 => { let values = &mut self.payload.__bindgen_anon_1.int2Value; let mut ir = ui.horizontal(|ui| { ui.add(CustomDragValue::new(&mut values[0])).changed() @@ -104,7 +120,7 @@ impl UIRenderer for Var { ir.response.changed = ir.inner; ir.response } - SHType_Int3 => { + SHTYPE_INT3 => { let values = &mut self.payload.__bindgen_anon_1.int3Value; let mut ir = ui.horizontal(|ui| { ui.add(CustomDragValue::new(&mut values[0])).changed() @@ -114,7 +130,7 @@ impl UIRenderer for Var { ir.response.changed = ir.inner; ir.response } - SHType_Int4 => { + SHTYPE_INT4 => { let values = &mut self.payload.__bindgen_anon_1.int4Value; let mut ir = ui.horizontal(|ui| { ui.add(CustomDragValue::new(&mut values[0])).changed() @@ -125,7 +141,7 @@ impl UIRenderer for Var { ir.response.changed = ir.inner; ir.response } - SHType_Int8 => { + SHTYPE_INT8 => { let values = &mut self.payload.__bindgen_anon_1.int8Value; let mut ir = ui.vertical(|ui| { ui.horizontal(|ui| { @@ -147,7 +163,7 @@ impl UIRenderer for Var { ir.response.changed = ir.inner; ir.response } - SHType_Int16 => { + SHTYPE_INT16 => { let values = &mut self.payload.__bindgen_anon_1.int16Value; let mut ir = ui.vertical(|ui| { ui.horizontal(|ui| { @@ -185,10 +201,10 @@ impl UIRenderer for Var { ir.response.changed = ir.inner; ir.response } - SHType_Float => ui.add(CustomDragValue::new( + SHTYPE_FLOAT => ui.add(CustomDragValue::new( &mut self.payload.__bindgen_anon_1.floatValue, )), - SHType_Float2 => { + SHTYPE_FLOAT2 => { let values = &mut self.payload.__bindgen_anon_1.float2Value; let mut ir = ui.horizontal(|ui| { ui.add(CustomDragValue::new(&mut values[0])).changed() @@ -197,7 +213,7 @@ impl UIRenderer for Var { ir.response.changed = ir.inner; ir.response } - SHType_Float3 => { + SHTYPE_FLOAT3 => { let values = &mut self.payload.__bindgen_anon_1.float3Value; let mut it = ui.horizontal(|ui| { ui.add(CustomDragValue::new(&mut values[0])).changed() @@ -207,7 +223,7 @@ impl UIRenderer for Var { it.response.changed = it.inner; it.response } - SHType_Float4 => { + SHTYPE_FLOAT4 => { let values = &mut self.payload.__bindgen_anon_1.float4Value; let mut it = ui.horizontal(|ui| { ui.add(CustomDragValue::new(&mut values[0])).changed() @@ -218,7 +234,7 @@ impl UIRenderer for Var { it.response.changed = it.inner; it.response } - SHType_Color => { + SHTYPE_COLOR => { let color = &mut self.payload.__bindgen_anon_1.colorValue; let mut srgba = [color.r, color.g, color.b, color.a]; let response = ui.color_edit_button_srgba_unmultiplied(&mut srgba); @@ -228,11 +244,11 @@ impl UIRenderer for Var { color.a = srgba[3]; response } - SHType_String | SHType_Path => { + SHTYPE_STRING | SHTYPE_PATH => { let mut buffer = MutVarTextBuffer(self); ui.text_edit_singleline(&mut buffer) } - SHType_Seq => { + SHTYPE_SEQ => { let seq = self .as_mut_seq() .expect("SHType was Seq, but failed to convert to Seq"); @@ -265,7 +281,9 @@ impl UIRenderer for Var { ir.header_response.changed = changed; ir.header_response } else { - ui.set_enabled(!read_only); + if read_only { + ui.disable(); + } if let Some(inner_type) = inner_type { if !read_only && ui.button("+").clicked() { let default_value = get_default_value(inner_type.basicType); @@ -275,7 +293,7 @@ impl UIRenderer for Var { ui.colored_label(Color32::YELLOW, "Seq: 0 items") } } - SHType_Table => { + SHTYPE_TABLE => { let mut table: Table = self .try_into() .expect("SHType was Table, but failed to convert to Table"); @@ -303,7 +321,9 @@ impl UIRenderer for Var { ir.header_response.changed = changed; ir.header_response } else { - ui.set_enabled(!read_only); + if read_only { + ui.disable(); + } ui.colored_label(Color32::YELLOW, "Empty table") } } diff --git a/shards/egui/src/widgets/variable.rs b/shards/egui/src/widgets/variable.rs index 3f85c9d717..954a171e26 100644 --- a/shards/egui/src/widgets/variable.rs +++ b/shards/egui/src/widgets/variable.rs @@ -1,5 +1,5 @@ use super::Variable; -use super::WireVariable; + use crate::util; use crate::EguiId; use crate::UIRenderer; @@ -14,12 +14,9 @@ use shards::types::InstanceData; use shards::types::OptionalString; use shards::types::ParamVar; use shards::types::Parameters; -use shards::types::RawString; -use shards::types::Table; use shards::types::Type; use shards::types::Types; use shards::types::Var; -use shards::types::WireRef; use shards::types::ANY_TYPES; use shards::types::BOOL_TYPES; use shards::types::{ExposedInfo, ExposedTypes}; @@ -27,12 +24,9 @@ use shards::util::from_raw_parts_allow_null; use shards::SHType_Seq; use std::cmp::Ordering; use std::ffi::CStr; -use std::os::raw::c_char; static ANY_VAR_ONLY_SLICE: &[Type] = &[common_type::any_var]; -static WIRE_VAR_TYPES: &[Type] = &[common_type::wire, common_type::string]; - lazy_static! { static ref VARIABLE_PARAMETERS: Parameters = vec![ ( @@ -50,14 +44,7 @@ lazy_static! { ]; } -static HEADERS_TYPES: &[Type] = &[ - common_type::none, - common_type::string_table, - common_type::string_table_var, -]; - extern "C" { - fn getWireVariable(wire: WireRef, name: *const c_char, nameLen: u32) -> *mut Var; fn triggerVarValueChange( context: *mut Context, name: *const Var, @@ -65,7 +52,6 @@ extern "C" { is_global: bool, var: *const Var, ); - fn getWireContext(wire: WireRef) -> *mut Context; } impl Default for Variable { @@ -235,7 +221,7 @@ impl LegacyShard for Variable { Ok(()) } - fn activate(&mut self, context: &Context, input: &Var) -> Result, &str> { + fn activate(&mut self, context: &Context, _input: &Var) -> Result, &str> { if let Some(ui) = util::get_current_parent_opt(self.parents.get())? { let inner_id = ui.id().with(EguiId::new(self, 0)); let label: &str = self.name.as_ref().try_into()?; diff --git a/shards/lang/src/ast.rs b/shards/lang/src/ast.rs index 840d4d5b93..837faa9de1 100644 --- a/shards/lang/src/ast.rs +++ b/shards/lang/src/ast.rs @@ -5,7 +5,7 @@ use core::{fmt, hash::Hash}; use pest::Position; use serde::{ser::SerializeStruct, Deserialize, Serialize}; use shards::{ - shlog_debug, types::Var, SHType_Bool, SHType_Bytes, SHType_Float, SHType_Int, SHType_None, + types::Var, SHType_Bool, SHType_Bytes, SHType_Float, SHType_Int, SHType_None, SHType_String, }; use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hasher}; @@ -403,7 +403,6 @@ impl Serialize for Program { where S: serde::Serializer, { - use serde::ser::SerializeStruct; let mut state = serializer.serialize_struct("Program", 2)?; state.serialize_field("metadata", &self.metadata)?; state.serialize_field("sequence", &self.sequence.statements)?; @@ -511,7 +510,6 @@ impl Serialize for Block { where S: serde::Serializer, { - use serde::ser::SerializeStruct; let mut state = serializer.serialize_struct("Block", 2)?; // Flatten the content diff --git a/shards/lang/src/cli.rs b/shards/lang/src/cli.rs index 0d8cd45c5b..42fda4531a 100644 --- a/shards/lang/src/cli.rs +++ b/shards/lang/src/cli.rs @@ -2,8 +2,8 @@ use crate::error::Error; use crate::read::{get_dependencies, read_with_env, ReadEnv}; use crate::{eval, formatter, Program}; use crate::{eval::eval, eval::new_cancellation_token, read::read}; -use clap::Parser; -use shards::core::Core; +use clap::{arg, Parser}; +use shards::core::{Core}; use shards::types::Mesh; use shards::util::from_raw_parts_allow_null; use shards::{ @@ -183,7 +183,7 @@ pub fn process_args(argc: i32, argv: *const *const c_char, no_cancellation: bool // Try to support a simple "shards script.shs" command line in case none of the above matched Err(orig_err) => match SimpleCLI::try_parse_from(args) { Ok(cli) => execute(&cli.run_args, cancellation_token), - Err(e) => Err(Box::new(orig_err) as Box), + Err(_e) => Err(Box::new(orig_err) as Box), }, }; diff --git a/shards/lang/src/eval.rs b/shards/lang/src/eval.rs index b6462689e3..b72145e4ba 100644 --- a/shards/lang/src/eval.rs +++ b/shards/lang/src/eval.rs @@ -2246,7 +2246,8 @@ fn create_shard_inner( let mut replacement_storage = None; let shard = if let Some(replacement) = get_replacement(shard, e) { - &replacement_storage.insert(replacement) + let stored = replacement_storage.insert(replacement); + stored } else { shard }; @@ -2984,7 +2985,8 @@ fn eval_pipeline( let mut replacement_storage = None; let func = if let Some(replacement) = get_replacement(func, e) { - &replacement_storage.insert(replacement) + let stored = replacement_storage.insert(replacement); + stored } else { func }; @@ -3761,7 +3763,7 @@ fn add_assignment_shard_no_suffix( fn eval_assignment( assignment: &Assignment, e: &mut EvalEnv, - cancellation_token: Arc, + _cancellation_token: Arc, ) -> Result<(), ShardsError> { let op = match assignment.kind { AssignmentKind::AssignRef => "Ref", diff --git a/shards/lang/src/formatter.rs b/shards/lang/src/formatter.rs index 3f93c4cbbb..ceed2d0469 100644 --- a/shards/lang/src/formatter.rs +++ b/shards/lang/src/formatter.rs @@ -777,12 +777,6 @@ pub fn format_str(input: &str) -> Result { Ok(String::from_utf8(buf.into_inner()?)?) } -fn strequal_ignore_line_endings(a: &str, b: &str) -> bool { - let a = a.replace("\r\n", "\n"); - let b = b.replace("\r\n", "\n"); - a == b -} - fn strequal(a: &str, b: &str) -> bool { a == b } diff --git a/shards/lang/src/read.rs b/shards/lang/src/read.rs index f7302a138a..d80eadf64c 100644 --- a/shards/lang/src/read.rs +++ b/shards/lang/src/read.rs @@ -5,7 +5,7 @@ use pest::iterators::Pair; use pest::Parser; use shards::shard::Shard; use shards::types::{ - common_type, AutoSeqVar, AutoTableVar, ClonedVar, Context, ExposedTypes, InstanceData, ParamVar, SeqVar, Type, Types, Var, FRAG_CC, SEQ_OF_STRINGS, SEQ_OF_STRINGS_TYPES, STRINGS_TYPES, STRING_TYPES, STRING_VAR_OR_NONE_SLICE + common_type, AutoSeqVar, AutoTableVar, ClonedVar, Context, ExposedTypes, InstanceData, ParamVar, SeqVar, Type, Types, Var, FRAG_CC, STRINGS_TYPES, STRING_TYPES, STRING_VAR_OR_NONE_SLICE }; use shards::{ fourCharacterCode, ref_counted_object_type_impl, shard, shard_impl, shlog_debug, shlog_error, @@ -132,7 +132,7 @@ fn process_assignment(pair: Pair, env: &mut ReadEnv) -> Result #include #include "../core/platform.hpp" +#include "process_time.hpp" #if SH_ANDROID #include @@ -182,6 +183,8 @@ struct Sinks { std::unique_lock lockUnique() { return std::unique_lock(lock); } std::shared_lock lockShared() { return std::shared_lock(lock); } + void initCustomFormatters() {} + void initStdErrSink() { #if !SH_EMSCRIPTEN if (stdErrSink) @@ -198,11 +201,8 @@ struct Sinks { distSink->remove_sink(logFileSink); #if defined(SHARDS_LOG_ROTATING_MAX_FILE_SIZE) && defined(SHARDS_LOG_ROTATING_MAX_FILES) - logFileSink = std::make_shared( - logFilePath.c_str(), - SHARDS_LOG_ROTATING_MAX_FILE_SIZE, - SHARDS_LOG_ROTATING_MAX_FILES, - true); + logFileSink = std::make_shared(logFilePath.c_str(), SHARDS_LOG_ROTATING_MAX_FILE_SIZE, + SHARDS_LOG_ROTATING_MAX_FILES, true); #else logFileSink = std::make_shared(logFilePath.c_str(), true); #endif @@ -229,13 +229,9 @@ Sinks &globalSinks() { return sinks; } -void flush() { - globalSinks().distSink->flush(); -} +void flush() { globalSinks().distSink->flush(); } -std::shared_ptr getDistSink() { - return globalSinks().distSink; -} +std::shared_ptr getDistSink() { return globalSinks().distSink; } void __init(Logger logger) { spdlog::register_logger(logger); @@ -274,11 +270,20 @@ void initFlush(Logger logger) { } } +static std::shared_ptr getTimeKeeper() { + static auto t = std::make_shared(); + return t; +} + void initLogFormat(Logger logger) { std::string varName = fmt::format("LOG_{}_FORMAT", logger->name()); + + auto formatter = std::make_unique(); + formatter->add_flag('P', getTimeKeeper()); + #if SHARDS_LOG_SDL if (const char *val = SDL_getenv(varName.c_str())) { - logger->set_pattern(val); + formatter->set_pattern(val); } else #endif { @@ -298,8 +303,10 @@ void initLogFormat(Logger logger) { #endif } - logger->set_pattern(logPattern); + formatter->set_pattern(logPattern); } + + logger->set_formatter(std::move(formatter)); } void initSinks(Logger logger) { diff --git a/shards/log/process_time.hpp b/shards/log/process_time.hpp new file mode 100644 index 0000000000..5cc40fa7c6 --- /dev/null +++ b/shards/log/process_time.hpp @@ -0,0 +1,46 @@ +#ifndef A3D46BB0_72BD_48CD_AC40_44503BFF61DB +#define A3D46BB0_72BD_48CD_AC40_44503BFF61DB + +#include +#include +#include +#include + +class TimeKeeper { +public: + TimeKeeper() { startTime = std::chrono::high_resolution_clock::now(); } + + double getElapsedTime() { + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = now - startTime; + return std::chrono::duration(elapsed).count(); + } + + std::chrono::high_resolution_clock::time_point startTime; +}; + +class ProcessTimeFlag : public spdlog::custom_flag_formatter { +public: + ProcessTimeFlag(std::shared_ptr timeKeeper) : timeKeeper(timeKeeper) {} + + void format(const spdlog::details::log_msg &, const std::tm &, spdlog::memory_buf_t &dest) override { + // Calculate elapsed time + auto elapsed = timeKeeper->getElapsedTime(); + + // Format with 2 decimal places + std::ostringstream ss; + ss << std::fixed << std::setprecision(2) << elapsed; + std::string time_str = ss.str(); + + dest.append(time_str.data(), time_str.data() + time_str.size()); + } + + std::unique_ptr clone() const override { + return spdlog::details::make_unique(timeKeeper); + } + +private: + std::shared_ptr timeKeeper; +}; + +#endif /* A3D46BB0_72BD_48CD_AC40_44503BFF61DB */ diff --git a/shards/modules/core/core.cpp b/shards/modules/core/core.cpp index cad082bd3d..e5599821fc 100644 --- a/shards/modules/core/core.cpp +++ b/shards/modules/core/core.cpp @@ -3224,6 +3224,8 @@ SHARDS_REGISTER_FN(core) { REGISTER_SHARD("ForEach", ForEachShard); REGISTER_SHARD("ForRange", ForRangeShard); + REGISTER_SHARD("IntRange", IntRangeShard); + REGISTER_SHARD("Map", Map); REGISTER_SHARD("Reduce", Reduce); REGISTER_SHARD("Erase", Erase); diff --git a/shards/modules/core/core.hpp b/shards/modules/core/core.hpp index 65559fef1b..bf1d99fb00 100644 --- a/shards/modules/core/core.hpp +++ b/shards/modules/core/core.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "time.hpp" #include @@ -4043,6 +4044,49 @@ struct ForRangeShard { } } }; +struct IntRangeShard { + PARAM_PARAMVAR(_start, "Start", "Starting value (inclusive)", {CoreInfo::IntOrIntVar}) + PARAM_PARAMVAR(_end, "End", "Ending value (exclusive)", {CoreInfo::IntOrIntVar}) + + PARAM_IMPL(PARAM_IMPL_FOR(_start), PARAM_IMPL_FOR(_end)) + + SeqVar _output{}; + + static SHOptionalString help() { return SHCCSTR("Returns a sequence of integers from Start (inclusive) to End (exclusive)"); } + + static SHTypesInfo inputTypes() { return CoreInfo::NoneType; } + static SHOptionalString inputHelp() { return SHCCSTR("Input is ignored"); } + + static SHTypesInfo outputTypes() { return CoreInfo::IntSeqType; } + static SHOptionalString outputHelp() { return SHCCSTR("Sequence of integers"); } + + PARAM_REQUIRED_VARIABLES(); + SHTypeInfo compose(const SHInstanceData &data) { + PARAM_COMPOSE_REQUIRED_VARIABLES(data); + if (_start.isNone() || _end.isNone()) { + throw ComposeError("IntRange requires both Start and End parameters to be set."); + } + return outputTypes().elements[0]; + } + + void warmup(SHContext *ctx) { PARAM_WARMUP(ctx); } + + void cleanup(SHContext *ctx) { PARAM_CLEANUP(ctx); } + + SHVar activate(SHContext *context, const SHVar &input) { + auto start = _start.get().payload.intValue; + auto end = _end.get().payload.intValue; + + int len = end - start; + _output.resize(len); + if (len > 0) { + for (int i = 0; i < len; i++) { + _output[i] = Var(start + i); + } + } + return _output; + } +}; struct Repeat { ShardsVar _blks{}; diff --git a/shards/modules/llm/embedd.cpp b/shards/modules/llm/embedd.cpp index 6c6a17c930..819fca06d8 100644 --- a/shards/modules/llm/embedd.cpp +++ b/shards/modules/llm/embedd.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -404,9 +405,41 @@ struct Embed { return _embeddings; } }; +void log_callback(ggml_log_level level, const char *text, void *user_data) { + static auto logger = shards::logging::getOrCreate("llama"); + spdlog::level::level_enum spdlog_level{}; + switch (level) { + case GGML_LOG_LEVEL_DEBUG: + spdlog_level = spdlog::level::debug; + break; + case GGML_LOG_LEVEL_INFO: + spdlog_level = spdlog::level::info; + break; + case GGML_LOG_LEVEL_WARN: + spdlog_level = spdlog::level::warn; + break; + case GGML_LOG_LEVEL_ERROR: + spdlog_level = spdlog::level::err; + break; + default: + spdlog_level = spdlog::level::info; + break; + } + std::string_view text_view{text}; + while (text_view.size() > 0) { + if (text_view.back() == '\n') { + text_view.remove_suffix(1); + } else { + break; + } + } + logger->log(spdlog_level, "{}", text_view); +} + } // namespace llm SHARDS_REGISTER_FN(llm) { + llama_log_set(&llm::log_callback, nullptr); REGISTER_SHARD("LLM.Model", llm::Model); REGISTER_SHARD("LLM.Context", llm::Context); REGISTER_SHARD("LLM.Tokenize", llm::Tokenize); diff --git a/shards/rust/src/shardsc.rs b/shards/rust/src/shardsc.rs index 2231c55e10..9f8b7a6c0c 100644 --- a/shards/rust/src/shardsc.rs +++ b/shards/rust/src/shardsc.rs @@ -1,2 +1,3 @@ #![allow(non_snake_case)] +#![allow(dead_code)] include!(concat!(env!("OUT_DIR"), "/shardsc.rs")); diff --git a/shards/rust/src/types.rs b/shards/rust/src/types.rs index 96b0b2607d..682dadffd6 100644 --- a/shards/rust/src/types.rs +++ b/shards/rust/src/types.rs @@ -3580,7 +3580,7 @@ impl Var { impl TryFrom<&Var> for SHString { type Error = &'static str; - + #[inline(always)] fn try_from(var: &Var) -> Result { if var.valueType != SHType_String @@ -4828,6 +4828,14 @@ impl Drop for ShardsVar { } } +impl Clone for ShardsVar { + fn clone(&self) -> Self { + let mut c = ShardsVar::default(); + c.set_param(&self.param.0).unwrap(); + c + } +} + unsafe extern "C" fn shardsvar_compose_cb( errorShard: *const Shard, errorTxt: SHStringWithLen, diff --git a/shards/rust_macro/src/lib.rs b/shards/rust_macro/src/lib.rs index 481ef7b53a..b6fbd1ddfd 100644 --- a/shards/rust_macro/src/lib.rs +++ b/shards/rust_macro/src/lib.rs @@ -74,7 +74,6 @@ struct ParamSingle { struct ParamSet { type_name: syn::Type, var_name: syn::Ident, - has_custom_interface: bool, } enum Param { @@ -473,7 +472,6 @@ fn parse_shard_fields<'a>( result.params.push(Param::Set(ParamSet { type_name: param_set_ty, var_name: fld.ident.clone().expect("Expected field name"), - has_custom_interface, })); result .warmables diff --git a/shards/tests/ui-table.shs b/shards/tests/ui-table.shs new file mode 100644 index 0000000000..a9dcd5ee82 --- /dev/null +++ b/shards/tests/ui-table.shs @@ -0,0 +1,223 @@ +@wire(main { + GFX.MainWindow( + Contents: { + Once({ + GFX.DrawQueue >= ui-draw-queue + GFX.UIPass(ui-draw-queue) >> render-steps + + ; Add storage for dropped items + null | Expect(@type(Type::Any)) >= dropped-item + }) + + UI( + UI.CentralPanel( + Contents: { + Once({ + Table(selection-set Type: @type({none: Type::Bool})) + Sequence(data-src Type: @type([{data: Type::String name: Type::String type: Type::Int id: Type::Int}])) + ForRange(1 16 { + = i + { + id: (i | Sub(1)) + data: (RandomInt | ToString) + type: (RandomInt(4)) + name: (Random.Name(2 "_") | String.ToLower) + } >> data-src + }) + false >= reversed + "" >= sorted-by-key + Count(data-src) | Memoize({= int-range-len | IntRange(0 int-range-len)}) >= int-range + }) + + + @template(toggle-sort-by [key] { + Cond([ + {sorted-by-key | Is(key) | And | reversed | Not} { + ; Switch to reversed + true > reversed + } + {sorted-by-key | Is(key) | And | reversed} { + ; Reset to default + "id" > sorted-by-key + false > reversed + } + {true} { + ; Sort by this key + key > sorted-by-key + false > reversed + }]) + Sort(int-range + Key: {= i + data-src | Take(i) | Take(sorted-by-key) + } + ) + }) + + @template(sort-by-button [key] { + Cond([ + {sorted-by-key | Is(key) | And | reversed | Not} { + UI.Button("⏷" {@toggle-sort-by(key)}) + } + {sorted-by-key | Is(key) | And | reversed} { + UI.Button("⏶" {@toggle-sort-by(key)}) + } + {true} { + UI.Button("➖" {@toggle-sort-by(key)}) + }]) + }) + + @template(is-selected [ss id] { + Get(ss id Default: false) + }) + + ; Count(int-range) ; Number of entries + UI.ScrollArea(MaxHeight: 300.0 Contents: { + int-range + UI.Table( + Striped: true + Resizable: true + RowHeight: 30.0 + Reversed: reversed + DoubleClicked: { + Log("Double clicked") + } + Clicked: { + = i + Log("Row clicked") + If({@is-selected(selection-set i)} { + Erase(i selection-set) + } { + true | Update(selection-set i) + } + ) + } + ContextMenu: { + = i + ["Item: " i] | String.Format | UI.Label + "Menu" | UI.Label + UI.Button("Close" {Log("Menu") | UI.CloseMenu}) + } + IsSelected: { + = i + @is-selected(selection-set i) + } + Columns: [ + { + = i + UI.TableHeader({ + "Name" | UI.Label + @sort-by-button("name") + }) + i | UI.DragAndDrop({ + UI.Frame({ + "icon" | UI.Label(Selectable: false) + } InnerMargin: @f4(4.0 4.0 0.0 0.0)) + }) + data-src | Take(i) | Take("name") | UI.Label + } + { + UI.TableHeader({ + "#" | UI.Label + @sort-by-button("id") + }) + ToString | UI.Label + } + { + UI.TableHeader({ + "Type" | UI.Label + @sort-by-button("type") + }) + = i + data-src | Take(i) | Take("type") | ToString | UI.Label + } + { + UI.TableHeader({"Data" | UI.Label}) + = i + data-src | Take(i) | Take("data") | UI.Label + }] + ) + }) + + + + ; Test table with all column settings + UI.Space(20.0) + 4 | UI.Table( + Resizable: true + Striped: true + Columns: [ + { + UI.TableHeader({ + "Auto Width" | UI.Label + } WidthType: "auto") + ToString | UI.Label + } + { + UI.TableHeader({ + "Initial Width" | UI.Label + } WidthType: "initial" Width: 150.0) + ToString | UI.Label + } + { + UI.TableHeader({ + "Exact Width" | UI.Label + } WidthType: "exact" Width: 100.0) + ToString | UI.Label + } + { + UI.TableHeader({ + "Remainder" | UI.Label + } WidthType: "remainder") + ToString | UI.Label + } + { + UI.TableHeader({ + "Min/Max Width" | UI.Label + } MinWidth: 100.0 MaxWidth: 200.0) + ToString | UI.Label + } + { + UI.TableHeader({ + "Clipped Long Content" | UI.Label + } Clip: true Width: 100.0) + "This is some very long content that should be clipped" | UI.Label + } + { + UI.TableHeader({ + "Not Resizable" | UI.Label + } Resizable: false Width: 100.0) + ToString | UI.Label + }] + ) + + ; Add drop box at bottom + UI.Space(20.0) + UI.DragAndDrop({ + UI.Frame( + InnerMargin: @f4(10) OuterMargin: @f4(0) Rounding: @f4(5) + FillColor: @color(40 40 40) + StrokeColor: @color(90 90 32) StrokeWidth: 2.0 + Contents: { + "Drop items here" | UI.Label + dropped-item | WhenNot({IsNone} { + ToString | UI.Label + }) + } + ) + } Drop: { + = item + Log("Dropping item") + item > dropped-item + } + ) + } + ) + ) | UI.Render(ui-draw-queue) + + GFX.Render(Steps: render-steps) + } + ) +} Looped: true) +@mesh(root) +@schedule(root main) +@run(root FPS: 60)