GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
Implement dynamic rounded corners for MenuBar and StatusBar widgets
src/backend/window_runner.rs | 15 ++-
src/color.rs | 52 ++++-----
src/config.rs | 103 ++++++++++++------
src/layout.rs | 190 +++++++++++++++++++++++++--------
src/widget/container/breadcrumb.rs | 4 +
src/widget/container/menu.rs | 39 ++++++-
src/widget/container/scroll_box.rs | 8 +-
src/widget/container/scrolling_list.rs | 4 +
src/widget/display/preview.rs | 2 +-
src/widget/display/status_bar.rs | 60 ++++++++++-
src/widget/input/button.rs | 4 +-
src/widget/input/keybinds_control.rs | 4 +-
12 files changed, 369 insertions(+), 116 deletions(-)
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 3f74c2c..c231b11 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -1028,6 +1028,10 @@ pub trait Application: Sized + 'static {
fn input_regions(&self) -> Option<Vec<(i32, i32, i32, i32)>> {
None
}
+
+ fn desired_size(&self) -> Option<(u32, u32)> {
+ None
+ }
fn ui_context(&self) -> Option<&crate::context::UiContext> {
None
@@ -1629,7 +1633,9 @@ impl<A: Application> CompositorHandler for EngineState<A> {
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
- ) {}
+ ) {
+ self.redraw = true;
+ }
fn surface_leave(
&mut self,
@@ -2376,6 +2382,13 @@ pub fn run<A: Application>() {
engine_state.redraw = true;
}
+ if let Some((w, h)) = engine_state.inner.desired_size() {
+ if (engine_state.logical_width - w as f32).abs() > 0.001 || (engine_state.logical_height - h as f32).abs() > 0.001 {
+ engine_state.resize(w as f32, h as f32);
+ engine_state.redraw = true;
+ }
+ }
+
if let Some(ref mut pk) = engine_state.pressed_key {
let now = std::time::Instant::now();
if now.duration_since(pk.first_pressed) >= KEY_REPEAT_DELAY {
diff --git a/src/color.rs b/src/color.rs
index ffbad7e..c2293db 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -37,9 +37,9 @@ static BACKPLATE_OPACITY: RwLock<Option<f32>> = RwLock::new(None);
static TOGGLE_ON_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_ON);
static TOGGLE_OFF_COLOR: RwLock<[f32; 4]> = RwLock::new(TOGGLE_OFF);
static TOGGLE_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.18, 0.18, 0.22, 1.0]);
-static SCROLLINGLIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
-static SCROLLINGLIST_ENTRY_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.04]);
-static SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.8]);
+static LIST_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 0.3]);
+static LIST_ENTRY_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.04]);
+static LIST_ENTRY_HIGHLIGHT_COLOR: RwLock<[f32; 4]> = RwLock::new([1.0, 1.0, 1.0, 0.8]);
static BREADCRUMB_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0]);
static POPOVER_BG_COLOR: RwLock<[f32; 4]> = RwLock::new([0.08, 0.08, 0.12, 1.0]);
static PAGE_COLOR: RwLock<[f32; 4]> = RwLock::new([0.0, 0.0, 0.0, 0.0]);
@@ -181,7 +181,7 @@ fn parse_and_set_colors(content: &str) {
if let Some(c) = get_color("/style/highlight/primary") {
if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() { *lock = [c[0], c[1], c[2], 0.12]; }
}
- if let Some(c) = get_color("/style/button/background").or_else(|| get_color("/layout/button_background_color")) {
+ if let Some(c) = get_color("/style/control/button/background").or_else(|| get_color("/layout/button_background_color")) {
if let Ok(mut lock) = BUTTON_BACKGROUND_COLOR.write() { *lock = c; }
}
if let Some(c) = get_color("/layout/menubar_tab_label_color").or_else(|| get_color("/layout/paginator_tab_label_color")) {
@@ -190,13 +190,13 @@ fn parse_and_set_colors(content: &str) {
if let Some(c) = get_color("/layout/toggle_enabled_color") {
if let Ok(mut lock) = TOGGLE_ON_COLOR.write() { *lock = c; }
}
- if let Some(c) = get_color("/style/toggle/disabled_color") {
+ if let Some(c) = get_color("/style/control/toggle/disabled_color") {
if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() { *lock = c; }
}
if let Some(c) = get_color("/layout/toggle_bg_color") {
if let Ok(mut lock) = TOGGLE_BG_COLOR.write() { *lock = c; }
}
- let parsed_scrollinglist_bg = get_color("/layout/scrollinglist_bg_color");
+ let parsed_list_bg = get_color("/layout/list_bg_color");
let parsed_breadcrumb_bg = get_color("/layout/breadcrumb_bg_color");
let parsed_popover_bg = get_color("/layout/popover_bg_color");
@@ -207,18 +207,18 @@ fn parse_and_set_colors(content: &str) {
if let Ok(mut lock) = LAYER_COLOR.write() { *lock = c; }
}
- if let Some(c) = parsed_scrollinglist_bg {
- if let Ok(mut lock) = SCROLLINGLIST_BG_COLOR.write() {
+ if let Some(c) = parsed_list_bg {
+ if let Ok(mut lock) = LIST_BG_COLOR.write() {
*lock = [c[0], c[1], c[2], 0.3];
}
}
- if let Some(c) = get_color("/layout/scrollinglist_entry_bg_color") {
- if let Ok(mut lock) = SCROLLINGLIST_ENTRY_BG_COLOR.write() {
+ if let Some(c) = get_color("/layout/list_entry_bg_color") {
+ if let Ok(mut lock) = LIST_ENTRY_BG_COLOR.write() {
*lock = c;
}
}
- if let Some(c) = get_color("/layout/scrollinglist_entry_highlight_color") {
- if let Ok(mut lock) = SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR.write() {
+ if let Some(c) = get_color("/layout/list_entry_highlight_color") {
+ if let Ok(mut lock) = LIST_ENTRY_HIGHLIGHT_COLOR.write() {
*lock = c;
}
}
@@ -226,7 +226,7 @@ fn parse_and_set_colors(content: &str) {
if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
*lock = [c[0], c[1], c[2], 1.0];
}
- } else if let Some(c) = parsed_scrollinglist_bg {
+ } else if let Some(c) = parsed_list_bg {
if let Ok(mut lock) = BREADCRUMB_BG_COLOR.write() {
*lock = [c[0], c[1], c[2], 1.0];
}
@@ -235,7 +235,7 @@ fn parse_and_set_colors(content: &str) {
if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
*lock = [c[0], c[1], c[2], 1.0];
}
- } else if let Some(c) = parsed_scrollinglist_bg {
+ } else if let Some(c) = parsed_list_bg {
if let Ok(mut lock) = POPOVER_BG_COLOR.write() {
*lock = [c[0], c[1], c[2], 1.0];
}
@@ -452,35 +452,35 @@ pub fn set_toggle_bg_color(color: [f32; 4]) {
}
}
-pub fn scrollinglist_bg_color() -> [f32; 4] {
+pub fn list_bg_color() -> [f32; 4] {
load_colors_once();
- *SCROLLINGLIST_BG_COLOR.read().unwrap()
+ *LIST_BG_COLOR.read().unwrap()
}
-pub fn set_scrollinglist_bg_color(color: [f32; 4]) {
- if let Ok(mut lock) = SCROLLINGLIST_BG_COLOR.write() {
+pub fn set_list_bg_color(color: [f32; 4]) {
+ if let Ok(mut lock) = LIST_BG_COLOR.write() {
*lock = [color[0], color[1], color[2], 0.3];
}
}
-pub fn scrollinglist_entry_bg_color() -> [f32; 4] {
+pub fn list_entry_bg_color() -> [f32; 4] {
load_colors_once();
- *SCROLLINGLIST_ENTRY_BG_COLOR.read().unwrap()
+ *LIST_ENTRY_BG_COLOR.read().unwrap()
}
-pub fn set_scrollinglist_entry_bg_color(color: [f32; 4]) {
- if let Ok(mut lock) = SCROLLINGLIST_ENTRY_BG_COLOR.write() {
+pub fn set_list_entry_bg_color(color: [f32; 4]) {
+ if let Ok(mut lock) = LIST_ENTRY_BG_COLOR.write() {
*lock = color;
}
}
-pub fn scrollinglist_entry_highlight_color() -> [f32; 4] {
+pub fn list_entry_highlight_color() -> [f32; 4] {
load_colors_once();
- *SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR.read().unwrap()
+ *LIST_ENTRY_HIGHLIGHT_COLOR.read().unwrap()
}
-pub fn set_scrollinglist_entry_highlight_color(color: [f32; 4]) {
- if let Ok(mut lock) = SCROLLINGLIST_ENTRY_HIGHLIGHT_COLOR.write() {
+pub fn set_list_entry_highlight_color(color: [f32; 4]) {
+ if let Ok(mut lock) = LIST_ENTRY_HIGHLIGHT_COLOR.write() {
*lock = color;
}
}
diff --git a/src/config.rs b/src/config.rs
index 825ef0d..4a55b4b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -69,7 +69,7 @@ fn kdl_to_json(doc: &kdl::KdlDocument) -> serde_json::Value {
}
}
} else {
- let list_names = ["keybind", "pointer_bind", "gesture_bind", "mode_rule", "tag_layout", "startup", "device"];
+ let list_names = ["key_bindings", "pointer_bind", "gesture_bind", "mode_rule", "tag_layout", "startup", "device"];
if list_names.contains(&name.as_str()) {
map.insert(name, serde_json::Value::Array(vec![val]));
} else {
@@ -144,31 +144,64 @@ pub fn parse_config_path(key: &str, default_section: &str) -> (String, String, O
}
}
-pub fn update_kdl_in_memory(doc: &mut kdl::KdlDocument, key: &str, value: &str, default_section: &str) -> bool {
- let (target_section, target_node, target_prop) = parse_config_path(key, default_section);
-
- let section_node = if let Some(node) = doc.nodes_mut().iter_mut().find(|n| n.name().value() == target_section) {
- node
+const PROP_NODES: &[&str] = &[
+ "gestures", "key_bindings", "pointer_bind", "gesture_bind",
+ "button", "button_strip", "dropdown", "toggle", "spinbox", "slider", "font_selector",
+ "status", "overlay", "backplate", "desktop", "list", "section", "textbox"
+];
+
+fn get_or_create_node_mut<'a>(doc: &'a mut kdl::KdlDocument, path: &[&str]) -> Option<&'a mut kdl::KdlNode> {
+ if path.is_empty() {
+ return None;
+ }
+ let segment = path[0];
+ let idx = if let Some(i) = doc.nodes().iter().position(|n| n.name().value() == segment) {
+ i
} else {
- if let Ok(new_node) = format!("{}\n", target_section).parse::<kdl::KdlNode>() {
- doc.nodes_mut().push(new_node);
- doc.nodes_mut().last_mut().unwrap()
- } else {
- return false;
- }
+ let new_node = format!("{}\n", segment).parse::<kdl::KdlNode>().ok()?;
+ doc.nodes_mut().push(new_node);
+ doc.nodes().len() - 1
};
+ if path.len() == 1 {
+ Some(&mut doc.nodes_mut()[idx])
+ } else {
+ let children = doc.nodes_mut()[idx].ensure_children();
+ get_or_create_node_mut(children, &path[1..])
+ }
+}
+
+fn get_node_ref<'a>(doc: &'a kdl::KdlDocument, path: &[&str]) -> Option<&'a kdl::KdlNode> {
+ if path.is_empty() {
+ return None;
+ }
+ let segment = path[0];
+ let node = doc.nodes().iter().find(|n| n.name().value() == segment)?;
+ if path.len() == 1 {
+ Some(node)
+ } else {
+ let children = node.children()?;
+ get_node_ref(children, &path[1..])
+ }
+}
+
+pub fn update_kdl_in_memory(doc: &mut kdl::KdlDocument, key: &str, value: &str, _default_section: &str) -> bool {
+ let parts: Vec<&str> = key.split('.').collect();
+ if parts.is_empty() {
+ return false;
+ }
- let children = section_node.ensure_children();
+ let is_property = parts.len() >= 2 && PROP_NODES.contains(&parts[parts.len() - 2]);
- let child_node = if let Some(child) = children.nodes_mut().iter_mut().find(|n| n.name().value() == target_node) {
- child
+ let (node_path, target_prop) = if is_property {
+ (&parts[0..parts.len() - 1], Some(parts[parts.len() - 1].to_string()))
} else {
- if let Ok(new_child) = format!("{}\n", target_node).parse::<kdl::KdlNode>() {
- children.nodes_mut().push(new_child);
- children.nodes_mut().last_mut().unwrap()
- } else {
- return false;
- }
+ (&parts[0..parts.len()], None)
+ };
+
+ let child_node = if let Some(node) = get_or_create_node_mut(doc, node_path) {
+ node
+ } else {
+ return false;
};
let existing_ty = if let Some(ref prop_name) = target_prop {
@@ -307,15 +340,21 @@ pub fn write_config_value(path: &str, key: &str, value: &str, default_section: &
pub fn get_kdl_type_annotation(kdl_content: &str, key_path: &str) -> Option<String> {
let doc: kdl::KdlDocument = kdl_content.parse().ok()?;
- let (target_section, target_node, target_prop) = parse_config_path(key_path, "");
-
- // Find the section node
- let section_node = doc.nodes().iter().find(|n| n.name().value() == target_section)?;
-
- // Find the child node
- let children = section_node.children()?;
- let child_node = children.nodes().iter().find(|n| n.name().value() == target_node)?;
-
+ let parts: Vec<&str> = key_path.split('.').collect();
+ if parts.is_empty() {
+ return None;
+ }
+
+ let is_property = parts.len() >= 2 && PROP_NODES.contains(&parts[parts.len() - 2]);
+
+ let (node_path, target_prop) = if is_property {
+ (&parts[0..parts.len() - 1], Some(parts[parts.len() - 1].to_string()))
+ } else {
+ (&parts[0..parts.len()], None)
+ };
+
+ let child_node = get_node_ref(&doc, node_path)?;
+
if let Some(prop_name) = target_prop {
let entry = child_node.entries().iter().find(|e| e.name().map(|n| n.value()) == Some(&prop_name))?;
entry.ty().map(|t| t.value().to_string())
@@ -348,11 +387,11 @@ mod tests {
#[test]
fn test_get_kdl_type_annotation() {
- let content = "input {\n accel_profile (\"menu:flat,adaptive,none,custom\")\"flat\"\n gestures pinch=(bool)true\n}\n";
+ let content = "input {\n accel_profile (\"menu:flat,adaptive,none,custom\")\"flat\"\n touchpad {\n gestures pinch=(bool)true\n }\n}\n";
let ty1 = get_kdl_type_annotation(content, "input.accel_profile");
assert_eq!(ty1, Some("menu:flat,adaptive,none,custom".to_string()));
- let ty2 = get_kdl_type_annotation(content, "input.gestures.pinch");
+ let ty2 = get_kdl_type_annotation(content, "input.touchpad.gestures.pinch");
assert_eq!(ty2, Some("bool".to_string()));
}
}
diff --git a/src/layout.rs b/src/layout.rs
index 4a1412c..8968c92 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -16,22 +16,38 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
}
_ => {
let flat_key = match prefix {
- "style.button.font" => "button_font",
- "style.button.padding" => "button_padding",
- "style.button_strip.font" => "button_strip_font",
- "style.button_strip.spacing" => "button_strip_spacing",
- "style.dropdown.height" => "dropdown_height",
- "style.font_selector.height" => "font_selector_height",
+ "style.list.font" => "list_font",
+ "style.control.breadcrumb.font" => "breadcrumb_font",
+ "style.control.breadcrumb.corner_radius" => "breadcrumb_corner_radius",
+ "style.control.button.font" => "button_font",
+ "style.control.button.padding" => "button_padding",
+ "style.control.button.corner_radius" => "button_corner_radius",
+ "style.list.corner_radius" => "list_corner_radius",
+ "style.textbox.corner_radius" => "textbox_corner_radius",
+ "style.control.dropdown.font" => "dropdown_font",
+ "style.control.font_selector.font" => "font_selector_font",
+ "style.control.slider.font" => "slider_font",
+ "style.control.spinbox.font" => "spinbox_font",
+ "style.section.font" => "section_label_font",
+ "style.textbox.font" => "textbox_font",
+ "style.control.button_strip.font" => "button_strip_font",
+ "style.control.button_strip.spacing" => "button_strip_spacing",
+ "style.control.dropdown.height" => "dropdown_height",
+ "style.control.dropdown.corner_radius" => "dropdown_corner_radius",
+ "style.control.font_selector.height" => "font_selector_height",
+ "style.control.font_selector.corner_radius" => "font_selector_corner_radius",
"style.label.font" => "label_font",
- "style.slider.height" => "slider_height",
- "style.slider.corner_radius" => "slider_corner_radius",
- "style.spinbox.height" => "spinbox_height",
- "style.spinbox.button_padding" => "spinbox_button_padding",
+ "style.control.slider.height" => "slider_height",
+ "style.control.slider.corner_radius" => "slider_corner_radius",
+ "style.control.spinbox.height" => "spinbox_height",
+ "style.control.spinbox.button_padding" => "spinbox_button_padding",
+ "style.control.spinbox.corner_radius" => "spinbox_corner_radius",
"style.textbox.height" => "textbox_height",
- "style.toggle.font" => "toggle_font",
- "style.toggle.height" => "toggle_height",
- "style.toggle.border_width" => "toggle_border_width",
- "style.toggle.disabled_color" => "toggle_disabled_color",
+ "style.control.toggle.font" => "toggle_font",
+ "style.control.toggle.height" => "toggle_height",
+ "style.control.toggle.border_width" => "toggle_border_width",
+ "style.control.toggle.disabled_color" => "toggle_disabled_color",
+ "style.control.toggle.corner_radius" => "toggle_corner_radius",
"style.status.normal_color" => "status_normal_color",
"style.status.background_color" => "status_background_color",
"style.status.background_blur" => "status_background_blur",
@@ -45,9 +61,13 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
"style.overlay.position" => "overlay_position",
"style.overlay.border_gap" => "overlay_border_gap",
"style.editor.last_page" => "last_page",
- "style.surfaces.desktop.background_color" => "desktop_background_color",
- "style.surfaces.desktop.grid_color" => "desktop_grid_color",
- "style.surfaces.desktop.line_width" => "desktop_line_width",
+ "style.surfaces.desktop.gap_color" => "desktop_gap_color",
+ "style.surfaces.desktop.cell_color" => "desktop_cell_color",
+ "style.surfaces.desktop.gap_width" => "desktop_gap_width",
+ "style.surfaces.desktop.cell_corner_radius" => "desktop_cell_corner_radius",
+ "style.surfaces.desktop.cell_fade_inset" => "desktop_cell_fade_inset",
+ "style.surfaces.desktop.enable_solid_color" => "desktop_enable_solid_color",
+ "style.surfaces.desktop.solid_color" => "desktop_solid_color",
"style.surfaces.plate.padding" => "plate_padding",
"style.surfaces.backplate.color" => "backplate_color",
"style.surfaces.backplate.blur" => "backplate_blur",
@@ -140,6 +160,8 @@ static FONT_SELECTOR_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
static DROPDOWN_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
static TOGGLE_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
static SLIDER_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
+static BREADCRUMB_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
+static LIST_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
static TOGGLE_BORDER_WIDTH: RwLock<f32> = RwLock::new(1.0);
static TOGGLE_FONT: RwLock<String> = RwLock::new(String::new());
static TOGGLE_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
@@ -160,9 +182,9 @@ static SPINBOX_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
static SLIDER_FONT: RwLock<String> = RwLock::new(String::new());
static SLIDER_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
static PLATE_CORNER_RADIUS: RwLock<f32> = RwLock::new(12.0);
-static SCROLLINGLIST_FONT: RwLock<String> = RwLock::new(String::new());
-static SCROLLINGLIST_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
-static SCROLLINGLIST_JUSTIFICATION: RwLock<u8> = RwLock::new(0);
+static LIST_FONT: RwLock<String> = RwLock::new(String::new());
+static LIST_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
+static LIST_JUSTIFICATION: RwLock<u8> = RwLock::new(0);
static PLATE_OPACITY: RwLock<f32> = RwLock::new(1.0);
static PAGE_OPACITY: RwLock<f32> = RwLock::new(1.0);
static LAYER_OPACITY: RwLock<f32> = RwLock::new(1.0);
@@ -201,7 +223,7 @@ pub fn reload_config() {
let mut textbox_font_changed = false;
let mut spinbox_font_changed = false;
let mut slider_font_changed = false;
- let mut scrollinglist_font_changed = false;
+ let mut list_font_changed = false;
for line in content.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("label_margin") {
@@ -312,6 +334,24 @@ pub fn reload_config() {
}
}
}
+ if let Some(rest) = trimmed.strip_prefix("breadcrumb_corner_radius") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ let val_str = rest.trim_end_matches('"').trim();
+ if let Ok(val) = val_str.parse::<f32>() {
+ if let Ok(mut lock) = BREADCRUMB_CORNER_RADIUS.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("list_corner_radius") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ let val_str = rest.trim_end_matches('"').trim();
+ if let Ok(val) = val_str.parse::<f32>() {
+ if let Ok(mut lock) = LIST_CORNER_RADIUS.write() {
+ *lock = val;
+ }
+ }
+ }
if let Some(rest) = trimmed.strip_prefix("font_selector_corner_radius") {
let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
let val_str = rest.trim_end_matches('"').trim();
@@ -713,26 +753,26 @@ pub fn reload_config() {
slider_font_changed = true;
}
}
- if let Some(rest) = trimmed.strip_prefix("scrollinglist_font") {
+ if let Some(rest) = trimmed.strip_prefix("list_font") {
let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
let rest = mod_rest(rest);
let font = rest.trim().to_string();
let mut changed = false;
- if let Ok(mut lock) = SCROLLINGLIST_FONT.write() {
+ if let Ok(mut lock) = LIST_FONT.write() {
if *lock != font {
*lock = font;
changed = true;
}
}
if changed {
- scrollinglist_font_changed = true;
+ list_font_changed = true;
}
}
- if let Some(rest) = trimmed.strip_prefix("scrollinglist_justification") {
+ if let Some(rest) = trimmed.strip_prefix("list_justification") {
let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
let val_str = rest.trim_end_matches('"').trim();
if let Ok(val) = val_str.parse::<u8>() {
- if let Ok(mut lock) = SCROLLINGLIST_JUSTIFICATION.write() {
+ if let Ok(mut lock) = LIST_JUSTIFICATION.write() {
*lock = val;
}
}
@@ -788,8 +828,8 @@ pub fn reload_config() {
*lock = None;
}
}
- if scrollinglist_font_changed {
- if let Ok(mut lock) = SCROLLINGLIST_FONT_CACHED.write() {
+ if list_font_changed {
+ if let Ok(mut lock) = LIST_FONT_CACHED.write() {
*lock = None;
}
}
@@ -1761,8 +1801,8 @@ pub fn set_dropdown_font(font: &str) {
}
}
-// ScrollingList Font
-pub fn scrollinglist_font() -> String {
+// List Font
+pub fn list_font() -> String {
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
@@ -1770,18 +1810,18 @@ pub fn scrollinglist_font() -> String {
if let Some(content) = read_config() {
for line in content.lines() {
let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("scrollinglist_font") {
+ if let Some(rest) = trimmed.strip_prefix("list_font") {
let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
let rest = mod_rest(rest);
font = rest.trim().to_string();
}
}
}
- if let Ok(mut lock) = SCROLLINGLIST_FONT.write() {
+ if let Ok(mut lock) = LIST_FONT.write() {
*lock = font;
}
});
- let lock = SCROLLINGLIST_FONT.read().unwrap();
+ let lock = LIST_FONT.read().unwrap();
if lock.is_empty() {
"Outfit".to_string()
} else {
@@ -1789,44 +1829,44 @@ pub fn scrollinglist_font() -> String {
}
}
-pub fn scrollinglist_font_parsed() -> (String, f32) {
- if let Ok(lock) = SCROLLINGLIST_FONT_CACHED.read() {
+pub fn list_font_parsed() -> (String, f32) {
+ if let Ok(lock) = LIST_FONT_CACHED.read() {
if let Some(ref val) = *lock {
return val.clone();
}
}
- let font_str = scrollinglist_font();
+ let font_str = list_font();
let parsed = parse_font_string(&font_str);
let size = parsed.1.unwrap_or(12.0);
let val = (parsed.0, size);
- if let Ok(mut lock) = SCROLLINGLIST_FONT_CACHED.write() {
+ if let Ok(mut lock) = LIST_FONT_CACHED.write() {
*lock = Some(val.clone());
}
val
}
-pub fn set_scrollinglist_font(font: &str) {
- if let Ok(mut lock) = SCROLLINGLIST_FONT.write() {
+pub fn set_list_font(font: &str) {
+ if let Ok(mut lock) = LIST_FONT.write() {
*lock = font.to_string();
}
- if let Ok(mut lock) = SCROLLINGLIST_FONT_CACHED.write() {
+ if let Ok(mut lock) = LIST_FONT_CACHED.write() {
*lock = None;
}
}
-// ScrollingList Justification
-pub fn scrollinglist_justification() -> u8 {
+// List Justification
+pub fn list_justification() -> u8 {
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
if let Some(content) = read_config() {
for line in content.lines() {
let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("scrollinglist_justification") {
+ if let Some(rest) = trimmed.strip_prefix("list_justification") {
let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
let val_str = rest.trim_end_matches('"').trim();
if let Ok(val) = val_str.parse::<u8>() {
- if let Ok(mut lock) = SCROLLINGLIST_JUSTIFICATION.write() {
+ if let Ok(mut lock) = LIST_JUSTIFICATION.write() {
*lock = val;
}
}
@@ -1834,11 +1874,11 @@ pub fn scrollinglist_justification() -> u8 {
}
}
});
- *SCROLLINGLIST_JUSTIFICATION.read().unwrap()
+ *LIST_JUSTIFICATION.read().unwrap()
}
-pub fn set_scrollinglist_justification(just: u8) {
- if let Ok(mut lock) = SCROLLINGLIST_JUSTIFICATION.write() {
+pub fn set_list_justification(just: u8) {
+ if let Ok(mut lock) = LIST_JUSTIFICATION.write() {
*lock = just;
}
}
@@ -2285,6 +2325,62 @@ pub fn set_textbox_corner_radius(radius: f32) {
}
}
+pub fn breadcrumb_corner_radius() -> f32 {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+ INIT.call_once(|| {
+ if let Some(content) = read_config() {
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("breadcrumb_corner_radius") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ let val_str = rest.trim_end_matches('"').trim();
+ if let Ok(val) = val_str.parse::<f32>() {
+ if let Ok(mut lock) = BREADCRUMB_CORNER_RADIUS.write() {
+ *lock = val;
+ }
+ }
+ }
+ }
+ }
+ });
+ *BREADCRUMB_CORNER_RADIUS.read().unwrap()
+}
+
+pub fn set_breadcrumb_corner_radius(radius: f32) {
+ if let Ok(mut lock) = BREADCRUMB_CORNER_RADIUS.write() {
+ *lock = radius;
+ }
+}
+
+pub fn list_corner_radius() -> f32 {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+ INIT.call_once(|| {
+ if let Some(content) = read_config() {
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("list_corner_radius") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ let val_str = rest.trim_end_matches('"').trim();
+ if let Ok(val) = val_str.parse::<f32>() {
+ if let Ok(mut lock) = LIST_CORNER_RADIUS.write() {
+ *lock = val;
+ }
+ }
+ }
+ }
+ }
+ });
+ *LIST_CORNER_RADIUS.read().unwrap()
+}
+
+pub fn set_list_corner_radius(radius: f32) {
+ if let Ok(mut lock) = LIST_CORNER_RADIUS.write() {
+ *lock = radius;
+ }
+}
+
pub fn font_selector_corner_radius() -> f32 {
use std::sync::Once;
static INIT: Once = Once::new();
diff --git a/src/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index d5c25b8..3476180 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -97,6 +97,10 @@ impl Element for Breadcrumb {
}
}
+ fn corner_radius(&self) -> f32 {
+ crate::layout::breadcrumb_corner_radius()
+ }
+
fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
let mut quads = Vec::new();
quads.push((self.x, self.y, self.w, self.h, self.color()));
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 46a282b..9921d12 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -431,9 +431,41 @@ impl Element for MenuBar {
}
fn color(&self) -> [f32; 4] {
- [0.0, 0.0, 0.0, 0.0]
+ self.color.unwrap_or_else(|| colors::sidebar_bg_color())
+ }
+
+ fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+ if let Some(p_ptr) = self.parent {
+ let is_bp = unsafe { (*p_ptr).is_backplate() };
+ if is_bp {
+ let (px, py, pw, ph) = unsafe { (*p_ptr).rect() };
+ let (x, y, w, h) = self.rect();
+ let is_at_top = (y - py).abs() < 0.1;
+ let is_at_bottom = (y + h - (py + ph)).abs() < 0.1;
+
+ if is_at_top && is_at_bottom {
+ let is_at_left = (x - px).abs() < 0.1;
+ let is_at_right = (x + w - (px + pw)).abs() < 0.1;
+ return (is_at_left, is_at_right, is_at_right, is_at_left);
+ } else if is_at_top {
+ return (true, true, false, false);
+ } else if is_at_bottom {
+ return (false, false, true, true);
+ }
+ }
+ }
+ (false, false, false, false)
+ }
+
+ fn corner_radius(&self) -> f32 {
+ if let Some(p_ptr) = self.parent {
+ unsafe { (*p_ptr).corner_radius() }
+ } else {
+ 0.0
+ }
}
+
fn set_hovered(&mut self, v: bool) {
self.base.hovered = v;
}
@@ -906,7 +938,10 @@ impl Element for MenuBar {
let mut quads = Vec::new();
// Draw main background
- quads.push((self.base.x, self.base.y, self.base.w, self.base.h, colors::sidebar_bg_color()));
+ let (r1, r2, r3, r4) = self.rounded_corners();
+ if !r1 && !r2 && !r3 && !r4 {
+ quads.push((self.base.x, self.base.y, self.base.w, self.base.h, self.color()));
+ }
// 1. Highlight the title on hover or open
if !self.context_options.is_empty() {
diff --git a/src/widget/container/scroll_box.rs b/src/widget/container/scroll_box.rs
index 8a38289..7ddcef4 100644
--- a/src/widget/container/scroll_box.rs
+++ b/src/widget/container/scroll_box.rs
@@ -63,7 +63,11 @@ impl Element for ScrollBox {
self.viewport_y = y + self.viewport_offset_y;
self.viewport_h = h + self.viewport_offset_h;
}
- fn color(&self) -> [f32; 4] { crate::color::scrollinglist_bg_color() }
+ fn color(&self) -> [f32; 4] { crate::color::list_bg_color() }
+
+ fn corner_radius(&self) -> f32 {
+ crate::layout::list_corner_radius()
+ }
fn focus(&mut self) {
focus::set_focused(self);
@@ -106,7 +110,7 @@ impl Element for ScrollBox {
let mut quads = Vec::new();
// Background
- quads.push((self.base.x, self.base.y, self.base.w, self.base.h, crate::color::scrollinglist_bg_color()));
+ quads.push((self.base.x, self.base.y, self.base.w, self.base.h, crate::color::list_bg_color()));
// Border lines
let box_border_color = if focus::is_focused(self) {
diff --git a/src/widget/container/scrolling_list.rs b/src/widget/container/scrolling_list.rs
index 6f95c91..9e1be06 100644
--- a/src/widget/container/scrolling_list.rs
+++ b/src/widget/container/scrolling_list.rs
@@ -59,6 +59,10 @@ impl Element for ScrollingList {
fn color(&self) -> [f32; 4] {
self.scroll_box.color()
}
+
+ fn corner_radius(&self) -> f32 {
+ crate::layout::list_corner_radius()
+ }
fn as_ptr(&self) -> *mut (dyn Element + 'static) {
self as *const Self as *mut Self as *mut (dyn Element + 'static)
}
diff --git a/src/widget/display/preview.rs b/src/widget/display/preview.rs
index 76bae42..f8f5637 100644
--- a/src/widget/display/preview.rs
+++ b/src/widget/display/preview.rs
@@ -256,7 +256,7 @@ impl PreviewState {
preview_sec.content_y = cy + half_h - pad - 12.0;
preview_sec.finish();
- let bg_color = color::scrollinglist_bg_color();
+ let bg_color = color::list_bg_color();
canvas.rect(bg_color, cx + 12.0, rect_y, cw - 24.0, rect_h);
if let Some(image_data) = &self.image_preview {
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 4f1ad19..fcad77b 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -1,8 +1,10 @@
use crate::colors;
use crate::widget::*;
use crate::widget::display::{TextLabel, make_widget_text_buffer};
+use crate::context::UiContext;
pub struct StatusBar {
+ pub base: Widget,
x: f32, y: f32, w: f32, h: f32,
hovered: bool,
pub text: String,
@@ -10,11 +12,13 @@ pub struct StatusBar {
pub text_offset_x: Option<f32>,
pub text_color: Option<[f32; 4]>,
pub bg_color: Option<[f32; 4]>,
+ pub parent: Option<*mut (dyn Element + 'static)>,
}
impl StatusBar {
pub fn new() -> Self {
Self {
+ base: Widget::new_rect(0.0, 0.0, 0.0, 0.0),
x: 0.0,
y: 0.0,
w: 0.0,
@@ -25,6 +29,7 @@ impl StatusBar {
text_offset_x: None,
text_color: None,
bg_color: None,
+ parent: None,
}
}
pub fn with_text(mut self, text: &str) -> Self {
@@ -55,8 +60,13 @@ impl StatusBar {
}
impl Element for StatusBar {
+ fn base(&self) -> Option<&Widget> { Some(&self.base) }
+ fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
- fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { self.x = x; self.y = y; self.w = w; self.h = h; }
+ fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+ self.x = x; self.y = y; self.w = w; self.h = h;
+ self.base.x = x; self.base.y = y; self.base.w = w; self.base.h = h;
+ }
fn as_ptr(&self) -> *mut (dyn Element + 'static) {
self as *const Self as *mut Self as *mut (dyn Element + 'static)
}
@@ -66,6 +76,54 @@ impl Element for StatusBar {
fn color(&self) -> [f32; 4] { self.bg_color.unwrap_or(colors::STATUS_BG) }
fn set_hovered(&mut self, v: bool) { self.hovered = v; }
fn hovered(&self) -> bool { self.hovered }
+
+ fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
+ self.parent
+ }
+
+ fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
+ self.parent = parent;
+ }
+
+ fn rounded_corners(&self) -> (bool, bool, bool, bool) {
+ if let Some(p_ptr) = self.parent {
+ let is_bp = unsafe { (*p_ptr).is_backplate() };
+ if is_bp {
+ let (px, py, pw, ph) = unsafe { (*p_ptr).rect() };
+ let (x, y, w, h) = self.rect();
+ let is_at_top = (y - py).abs() < 0.1;
+ let is_at_bottom = (y + h - (py + ph)).abs() < 0.1;
+
+ if is_at_top && is_at_bottom {
+ let is_at_left = (x - px).abs() < 0.1;
+ let is_at_right = (x + w - (px + pw)).abs() < 0.1;
+ return (is_at_left, is_at_right, is_at_right, is_at_left);
+ } else if is_at_top {
+ return (true, true, false, false);
+ } else if is_at_bottom {
+ return (false, false, true, true);
+ }
+ }
+ }
+ (false, false, false, false)
+ }
+
+ fn corner_radius(&self) -> f32 {
+ if let Some(p_ptr) = self.parent {
+ unsafe { (*p_ptr).corner_radius() }
+ } else {
+ 0.0
+ }
+ }
+
+ fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+ let (r1, r2, r3, r4) = self.rounded_corners();
+ if !r1 && !r2 && !r3 && !r4 {
+ vec![(self.x, self.y, self.w, self.h, self.color())]
+ } else {
+ Vec::new()
+ }
+ }
fn set_text(&mut self, text: &str) {
if self.text != text {
self.text = text.to_string();
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index bf7dfee..1da0ab8 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -255,7 +255,7 @@ impl Element for Button {
}
};
let justify = if self.kind == ButtonKind::ListRow {
- match crate::layout::scrollinglist_justification() {
+ match crate::layout::list_justification() {
0 => Justification::Left,
2 => Justification::Right,
_ => Justification::Center,
@@ -307,7 +307,7 @@ impl Element for Button {
fn widget_font(&self) -> Option<String> {
if self.kind == ButtonKind::ListRow {
- Some(crate::layout::scrollinglist_font())
+ Some(crate::layout::list_font())
} else {
Some(crate::layout::button_font())
}
diff --git a/src/widget/input/keybinds_control.rs b/src/widget/input/keybinds_control.rs
index 219509e..aa631ca 100644
--- a/src/widget/input/keybinds_control.rs
+++ b/src/widget/input/keybinds_control.rs
@@ -57,7 +57,7 @@ impl KeybindsControl {
let content = std::fs::read_to_string(&path).unwrap_or_default();
let val: serde_json::Value = serde_json::from_str(&content).unwrap_or_default();
let mut loaded = Vec::new();
- if let Some(arr) = val.get("keybind").and_then(|k| k.as_array()) {
+ if let Some(arr) = val.get("key_bindings").and_then(|k| k.as_array()) {
for v in arr {
let mods = v.get("mods").and_then(|m| m.as_str()).unwrap_or("").to_string();
let key = v.get("key").and_then(|k| k.as_str()).unwrap_or("").to_string();
@@ -123,7 +123,7 @@ impl KeybindsControl {
}
if let Some(obj) = val.as_object_mut() {
- obj.insert("keybind".to_string(), serde_json::Value::Array(keybinds));
+ obj.insert("key_bindings".to_string(), serde_json::Value::Array(keybinds));
}
if let Ok(updated_str) = serde_json::to_string_pretty(&val) {