GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
Fix duplicate overlapping menu bar titles when hidden
src/color.rs | 343 ++++++++++------------------------
src/layout.rs | 264 ++++++++++++++++++++++++++
src/widget/container/menu.rs | 276 +++++++++++++++++----------
src/widget/container/paginator.rs | 223 +++++++++++++++++++---
src/widget/container/parameters_bg.rs | 2 +-
src/widget/container/plate.rs | 48 ++++-
src/widget/container/spreadsheet.rs | 2 +-
src/widget/display/graph.rs | 2 +-
src/widget/display/text_label.rs | 5 +-
src/widget/input/dropdown.rs | 2 +-
src/widget/mod.rs | 1 +
11 files changed, 797 insertions(+), 371 deletions(-)
diff --git a/src/color.rs b/src/color.rs
index cb541cf..f90496e 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -67,34 +67,105 @@ pub fn node_drag_color() -> [f32; 4] {
]
}
-pub fn page_low_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("page_low_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = PAGE_LOW_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
+fn read_config() -> Option<String> {
+ let paths = [
+ "/home/lsgalante/.config/cce/config.toml",
+ "/home/lsgalante/.config/ccec/config.toml",
+ ];
+ for path in &paths {
+ if let Ok(content) = std::fs::read_to_string(path) {
+ return Some(content);
+ }
+ }
+ None
+}
+
+fn parse_and_set_colors(content: &str) {
+ let mut in_transparency = false;
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if trimmed == "[transparency]" {
+ in_transparency = true;
+ continue;
+ }
+ if trimmed.starts_with('[') && in_transparency {
+ in_transparency = false;
+ }
+
+ if in_transparency && trimmed.starts_with("opacity") {
+ if let Some(val) = trimmed.split('=').nth(1) {
+ if let Ok(o) = val.trim().parse::<f32>() {
+ if let Ok(mut lock) = OPACITY.write() {
+ *lock = Some(o.clamp(0.0, 1.0));
+ }
+ }
+ }
+ }
+
+ // Parse hex colors helper
+ let parse_hex = |trimmed_line: &str, prefix: &str| -> Option<[f32; 4]> {
+ if let Some(rest) = trimmed_line.strip_prefix(prefix) {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
+ if hex.len() >= 6 {
+ if let (Ok(r), Ok(g), Ok(b)) = (
+ u8::from_str_radix(&hex[0..2], 16),
+ u8::from_str_radix(&hex[2..4], 16),
+ u8::from_str_radix(&hex[4..6], 16),
+ ) {
+ let r_f = srgb_to_linear(r as f32 / 255.0);
+ let g_f = srgb_to_linear(g as f32 / 255.0);
+ let b_f = srgb_to_linear(b as f32 / 255.0);
+ return Some([r_f, g_f, b_f, 1.0]);
}
}
}
+ None
+ };
+
+ if let Some(c) = parse_hex(trimmed, "page_low_color") {
+ if let Ok(mut lock) = PAGE_LOW_COLOR.write() { *lock = c; }
+ }
+ if let Some(c) = parse_hex(trimmed, "color_borders_color") {
+ if let Ok(mut lock) = COLOR_BORDERS_COLOR.write() { *lock = c; }
+ }
+ if let Some(c) = parse_hex(trimmed, "slider_track_color") {
+ if let Ok(mut lock) = SLIDER_TRACK_COLOR.write() { *lock = c; }
+ }
+ if let Some(c) = parse_hex(trimmed, "paginator_sidebar_color") {
+ if let Ok(mut lock) = SIDEBAR_BG_COLOR.write() { *lock = c; }
+ }
+ if let Some(c) = parse_hex(trimmed, "primary_highlight_color") {
+ if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() { *lock = [c[0], c[1], c[2], 0.12]; }
+ }
+ if let Some(c) = parse_hex(trimmed, "paginator_tab_label_color") {
+ if let Ok(mut lock) = PAGINATOR_TAB_LABEL_COLOR.write() { *lock = c; }
+ }
+ if let Some(c) = parse_hex(trimmed, "toggle_enabled_color") {
+ if let Ok(mut lock) = TOGGLE_ON_COLOR.write() { *lock = c; }
+ }
+ if let Some(c) = parse_hex(trimmed, "toggle_disabled_color") {
+ if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() { *lock = c; }
+ }
+ }
+}
+
+fn load_colors_once() {
+ use std::sync::Once;
+ static INIT: Once = Once::new();
+ INIT.call_once(|| {
+ if let Some(content) = read_config() {
+ parse_and_set_colors(&content);
}
});
+}
+
+pub fn reload_colors(content: &str) {
+ parse_and_set_colors(content);
+}
+
+pub fn page_low_color() -> [f32; 4] {
+ load_colors_once();
let mut color = *PAGE_LOW_COLOR.read().unwrap();
if let Some(opacity) = read_opacity_if_configured() {
color[3] = opacity;
@@ -109,33 +180,7 @@ pub fn set_page_low_color(color: [f32; 4]) {
}
pub fn color_borders_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("color_borders_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = COLOR_BORDERS_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*COLOR_BORDERS_COLOR.read().unwrap()
}
@@ -146,33 +191,7 @@ pub fn set_color_borders_color(color: [f32; 4]) {
}
pub fn slider_track() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("slider_track_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = SLIDER_TRACK_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*SLIDER_TRACK_COLOR.read().unwrap()
}
@@ -239,33 +258,7 @@ pub fn to_srgb(color: [f32; 4]) -> [f32; 4] {
}
pub fn sidebar_bg_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("paginator_sidebar_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = SIDEBAR_BG_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
let mut color = *SIDEBAR_BG_COLOR.read().unwrap();
if let Some(opacity) = read_opacity_if_configured() {
color[3] = opacity;
@@ -280,33 +273,7 @@ pub fn set_sidebar_bg_color(color: [f32; 4]) {
}
pub fn highlight_primary_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("primary_highlight_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = HIGHLIGHT_PRIMARY_COLOR.write() {
- *lock = [r_f, g_f, b_f, 0.12];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*HIGHLIGHT_PRIMARY_COLOR.read().unwrap()
}
@@ -317,33 +284,7 @@ pub fn set_highlight_primary_color(color: [f32; 4]) {
}
pub fn paginator_tab_label_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("paginator_tab_label_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = PAGINATOR_TAB_LABEL_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*PAGINATOR_TAB_LABEL_COLOR.read().unwrap()
}
@@ -354,64 +295,12 @@ pub fn set_paginator_tab_label_color(color: [f32; 4]) {
}
pub fn read_opacity_if_configured() -> Option<f32> {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[transparency]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("opacity") {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(o) = val.trim().parse::<f32>() {
- if let Ok(mut lock) = OPACITY.write() {
- *lock = Some(o.clamp(0.0, 1.0));
- }
- break;
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*OPACITY.read().unwrap()
}
pub fn toggle_on_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("toggle_enabled_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = TOGGLE_ON_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*TOGGLE_ON_COLOR.read().unwrap()
}
@@ -422,33 +311,7 @@ pub fn set_toggle_on_color(color: [f32; 4]) {
}
pub fn toggle_off_color() -> [f32; 4] {
- use std::sync::Once;
- static INIT: Once = Once::new();
- INIT.call_once(|| {
- if let Ok(content) = std::fs::read_to_string("/home/lsgalante/.config/ccec/config.toml") {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix("toggle_disabled_color") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- if hex.len() >= 6 {
- if let (Ok(r), Ok(g), Ok(b)) = (
- u8::from_str_radix(&hex[0..2], 16),
- u8::from_str_radix(&hex[2..4], 16),
- u8::from_str_radix(&hex[4..6], 16),
- ) {
- let r_f = srgb_to_linear(r as f32 / 255.0);
- let g_f = srgb_to_linear(g as f32 / 255.0);
- let b_f = srgb_to_linear(b as f32 / 255.0);
- if let Ok(mut lock) = TOGGLE_OFF_COLOR.write() {
- *lock = [r_f, g_f, b_f, 1.0];
- }
- }
- }
- }
- }
- }
- });
+ load_colors_once();
*TOGGLE_OFF_COLOR.read().unwrap()
}
@@ -476,5 +339,3 @@ pub fn active_theme() -> Theme {
hover_overlay: [1.0, 1.0, 1.0, 0.08],
}
}
-
-
diff --git a/src/layout.rs b/src/layout.rs
index f47ed8b..45a92b5 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -53,6 +53,270 @@ static NESTED_SECTION_LABEL_ALIGNMENT: RwLock<u8> = RwLock::new(0);
static LABEL_MARGIN: RwLock<f32> = RwLock::new(6.0);
+pub fn reload_config() {
+ if let Some(content) = read_config() {
+ let mut menubar_font_changed = false;
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("label_margin") {
+ 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) = LABEL_MARGIN.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("nested_section_label_alignment") {
+ 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) = NESTED_SECTION_LABEL_ALIGNMENT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("nested_section_label_offset") {
+ 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) = NESTED_SECTION_LABEL_OFFSET.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("plate_padding") {
+ 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) = PLATE_PADDING.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("page_margin") {
+ 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) = PAGE_MARGIN.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("grid_min_col_width") {
+ 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) = GRID_MIN_COL_WIDTH.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("section_padding") {
+ 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) = SECTION_PADDING.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("spinbox_height") {
+ 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) = SPINBOX_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("toggle_height") {
+ 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) = TOGGLE_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("color_selector_height") {
+ 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) = COLOR_SELECTOR_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("font_selector_height") {
+ 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) = FONT_SELECTOR_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("color_selector_font") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+ let rest = rest.trim();
+ let val_str = if rest.starts_with('"') && rest.ends_with('"') && rest.len() >= 2 {
+ &rest[1..rest.len() - 1]
+ } else {
+ rest
+ };
+ let font = val_str.trim().to_string();
+ if let Ok(mut lock) = COLOR_SELECTOR_FONT.write() {
+ *lock = font;
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("menubar_font") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+ let rest = rest.trim();
+ let val_str = if rest.starts_with('"') && rest.ends_with('"') && rest.len() >= 2 {
+ &rest[1..rest.len() - 1]
+ } else {
+ rest
+ };
+ let font = val_str.trim().to_string();
+ let mut changed = false;
+ if let Ok(mut lock) = MENUBAR_FONT.write() {
+ if *lock != font {
+ *lock = font;
+ changed = true;
+ }
+ }
+ if changed {
+ menubar_font_changed = true;
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("section_label_font") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+ let rest = mod_rest(rest);
+ let font = rest.trim().to_string();
+ if let Ok(mut lock) = SECTION_LABEL_FONT.write() {
+ *lock = font;
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("nested_section_label_font") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
+ let rest = mod_rest(rest);
+ let font = rest.trim().to_string();
+ if let Ok(mut lock) = NESTED_SECTION_LABEL_FONT.write() {
+ *lock = font;
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("color_selector_preview_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) = COLOR_SELECTOR_PREVIEW_CORNER_RADIUS.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("color_selector_preview_margin") {
+ 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) = COLOR_SELECTOR_PREVIEW_MARGIN.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("paginator_tab_margin_x") {
+ 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) = PAGINATOR_TAB_MARGIN_X.write() {
+ *lock = val;
+ }
+ }
+ } else if let Some(rest) = trimmed.strip_prefix("paginator_tab_margin") {
+ 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) = PAGINATOR_TAB_MARGIN_X.write() {
+ *lock = val;
+ }
+ if let Ok(mut lock) = PAGINATOR_TAB_MARGIN_Y.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("paginator_tab_margin_y") {
+ 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) = PAGINATOR_TAB_MARGIN_Y.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("paginator_tab_padding_x") {
+ 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) = PAGINATOR_TAB_PADDING_X.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("paginator_tab_padding_y") {
+ 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) = PAGINATOR_TAB_PADDING_Y.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("textbox_height") {
+ 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) = TEXTBOX_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("dropdown_height") {
+ 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) = DROPDOWN_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ if let Some(rest) = trimmed.strip_prefix("slider_height") {
+ 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) = SLIDER_HEIGHT.write() {
+ *lock = val;
+ }
+ }
+ }
+ }
+ if menubar_font_changed {
+ if let Ok(mut lock) = MENUBAR_FONT_CACHED.write() {
+ *lock = None;
+ }
+ }
+ crate::color::reload_colors(&content);
+ }
+}
+
+fn mod_rest(rest: &str) -> &str {
+ let rest = rest.trim();
+ if rest.starts_with('"') && rest.ends_with('"') && rest.len() >= 2 {
+ &rest[1..rest.len() - 1]
+ } else {
+ rest
+ }
+}
+
pub fn label_margin() -> f32 {
use std::sync::Once;
static INIT: Once = Once::new();
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index fddd71d..0abf214 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -3,8 +3,7 @@ use crate::widget::*;
use crate::widget::display::{make_widget_text_buffer, TextLabel};
pub struct MenuBar {
- x: f32, y: f32, w: f32, h: f32,
- hovering: bool,
+ pub base: Plate,
pub title: String,
pub menus: Vec<Box<Menu>>,
pub menu_items: Vec<String>,
@@ -17,22 +16,19 @@ pub struct MenuBar {
pub clicked_dropdown: Option<(usize, usize)>,
pub was_open: Option<usize>,
pub vertical: bool,
- pub visible: bool,
pub focused: bool,
pub z_level: i32,
pub center_items: bool,
- pub curved_circle: Option<(f32, f32, f32)>,
pub title_pos: Option<(f32, f32)>,
pub title_buf: Option<glyphon::Buffer>,
pub curved_title_char_bufs: Vec<glyphon::Buffer>,
- pub network_opacity: f32,
pub font_family: String,
pub label: Option<String>,
}
impl MenuBar {
pub fn set_curved_circle(&mut self, circle: Option<(f32, f32, f32)>) {
- self.curved_circle = circle;
+ self.base.curved_circle = circle;
if circle.is_none() {
for menu in &mut self.menus {
menu.curved_arc = None;
@@ -41,12 +37,17 @@ impl MenuBar {
}
pub fn set_network_opacity(&mut self, opacity: f32) {
- self.network_opacity = opacity;
+ self.base.set_network_opacity(opacity);
}
pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
+ let mut base_plate = Plate::new(x, y, w, h)
+ .with_color(colors::PANEL_MENU_BG)
+ .with_draggable(false);
+ base_plate.blur = true;
+
Self {
- x, y, w, h, hovering: false,
+ base: base_plate,
title: String::new(),
menus: Vec::new(),
menu_items: Vec::new(),
@@ -59,15 +60,12 @@ impl MenuBar {
clicked_dropdown: None,
was_open: None,
vertical: false,
- visible: true,
focused: false,
z_level: 100,
center_items: false,
- curved_circle: None,
title_pos: None,
title_buf: None,
curved_title_char_bufs: Vec::new(),
- network_opacity: 1.0,
font_family: crate::layout::menubar_font(),
label: None,
}
@@ -75,11 +73,13 @@ impl MenuBar {
pub fn with_label(mut self, label: &str) -> Self {
self.label = Some(label.to_string());
+ self.base.base.label = Some(label.to_string());
self
}
pub fn set_label(&mut self, label: &str) {
self.label = Some(label.to_string());
+ self.base.base.label = Some(label.to_string());
}
pub fn with_center_items(mut self, center: bool) -> Self {
@@ -145,6 +145,12 @@ impl MenuBar {
fn item_y_vertical(&self, idx: usize) -> f32 {
let mut y = 8.0;
+ if let Some(ref label) = self.label {
+ let font_size = 12.0;
+ let line_height = font_size * 1.2;
+ let label_h = label.chars().count() as f32 * line_height;
+ y += label_h + 8.0;
+ }
if !self.title.is_empty() {
let font_size = 12.0;
let line_height = font_size * 1.2;
@@ -165,36 +171,50 @@ impl Element for MenuBar {
self as *const Self as *mut Self as *mut (dyn Element + 'static)
}
+ fn base(&self) -> Option<&Widget> { Some(&self.base.base) }
+ fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base.base) }
+
fn label(&self) -> Option<String> {
self.label.clone()
}
fn set_text(&mut self, text: &str) {
self.label = Some(text.to_string());
+ self.base.base.label = Some(text.to_string());
}
fn rect(&self) -> (f32, f32, f32, f32) {
- if !self.visible {
+ if !self.base.visible {
return (0.0, 0.0, 0.0, 0.0);
}
if self.vertical {
let total_h = if self.menus.is_empty() {
- self.h
+ let mut h = 16.0;
+ if let Some(ref label) = self.label {
+ let font_size = 12.0;
+ let line_height = font_size * 1.2;
+ let label_h = label.chars().count() as f32 * line_height;
+ h += label_h + 8.0;
+ }
+ if !self.title.is_empty() {
+ let font_size = 12.0;
+ let line_height = font_size * 1.2;
+ let title_h = self.title.chars().count() as f32 * line_height;
+ h += title_h + 8.0;
+ }
+ h
} else {
let last_idx = self.menus.len() - 1;
self.item_y_vertical(last_idx) + self.item_h_vertical(last_idx)
};
- (self.x, self.y, self.w, total_h)
+ (self.base.base.x, self.base.base.y, self.base.base.w, total_h)
} else {
- (self.x, self.y, self.w, self.h)
+ (self.base.base.x, self.base.base.y, self.base.base.w, self.base.base.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.set_rect(x, y, w, h);
let parent_ptr = self as *mut MenuBar as *mut (dyn Element + 'static);
@@ -202,10 +222,15 @@ impl Element for MenuBar {
let (_, font_size_opt) = crate::layout::parse_font_string(&font_setting);
let font_size = font_size_opt.unwrap_or(12.0);
let char_w = 7.5 * (font_size / 12.0);
- let ih = font_size + 12.0;
if self.vertical {
let mut cy = 8.0;
+ if let Some(ref label) = self.label {
+ let font_size = 12.0;
+ let line_height = font_size * 1.2;
+ let label_h = label.chars().count() as f32 * line_height;
+ cy += label_h + 8.0;
+ }
if !self.title.is_empty() {
let font_size = 12.0;
let line_height = font_size * 1.2;
@@ -224,7 +249,7 @@ impl Element for MenuBar {
}
} else {
let padding_x = crate::layout::paginator_tab_padding_x();
- if let Some((ccx, ccy, ccr)) = self.curved_circle {
+ if let Some((ccx, ccy, ccr)) = self.base.curved_circle {
let r_mid = ccr - h / 2.0;
let mut total_width = 8.0;
if !self.title.is_empty() {
@@ -276,8 +301,8 @@ impl Element for MenuBar {
for menu in &self.menus {
total_width += menu.active_title().len() as f32 * char_w + 2.0 * padding_x;
}
- if self.w > total_width {
- cx = (self.w - total_width) / 2.0;
+ if self.base.base.w > total_width {
+ cx = (self.base.base.w - total_width) / 2.0;
}
}
if !self.title.is_empty() {
@@ -295,32 +320,33 @@ impl Element for MenuBar {
}
fn color(&self) -> [f32; 4] {
- if !self.visible {
+ if !self.base.visible {
[0.0, 0.0, 0.0, 0.0]
} else if self.focused {
let mut c = colors::PANEL_MENU_FOCUSED;
- c[3] *= self.network_opacity;
+ c[3] *= self.base.network_opacity;
+ if self.base.blur {
+ c[3] = -c[3].abs();
+ }
c
} else {
- let mut c = colors::PANEL_MENU_BG;
- c[3] *= self.network_opacity;
- c
+ self.base.color()
}
}
fn set_hovered(&mut self, v: bool) {
- self.hovering = v;
+ self.base.base.hovered = v;
}
fn hovered(&self) -> bool {
- self.hovering
+ self.base.base.hovered
}
fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
- if !self.visible {
+ if !self.base.visible {
return false;
}
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
let font_setting = crate::layout::menubar_font();
@@ -328,18 +354,18 @@ impl Element for MenuBar {
let font_size = font_size_opt.unwrap_or(12.0);
let char_w = 7.5 * (font_size / 12.0);
- if let Some((ccx, ccy, ccr)) = self.curved_circle {
+ if let Some((ccx, ccy, ccr)) = self.base.curved_circle {
let dx = px - ccx;
let dy = py - ccy;
let dist = (dx * dx + dy * dy).sqrt();
- if dist >= ccr - self.h && dist <= ccr {
+ if dist >= ccr - self.base.base.h && dist <= ccr {
let angle = dy.atan2(dx);
let mut norm_angle = angle;
if norm_angle < 0.0 {
norm_angle += 2.0 * std::f32::consts::PI;
}
- let r_mid = ccr - self.h / 2.0;
+ let r_mid = ccr - self.base.base.h / 2.0;
let mut total_width = 8.0;
if !self.title.is_empty() {
total_width += self.title.len() as f32 * char_w + 24.0;
@@ -376,7 +402,7 @@ impl Element for MenuBar {
}
fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
- if !self.visible {
+ if !self.base.visible {
return false;
}
let (rx, ry, rw, rh) = self.rect();
@@ -396,7 +422,7 @@ impl Element for MenuBar {
}
fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32, ctx: &mut UiContext) -> bool {
- if !self.visible {
+ if !self.base.visible {
return false;
}
let (rx, ry, rw, rh) = self.rect();
@@ -497,11 +523,11 @@ impl Element for MenuBar {
}
fn is_menu_bar(&self) -> bool {
- self.visible
+ self.base.visible
}
fn get_menu_items_at(&self, px: f32, py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> {
- if !self.visible {
+ if !self.base.visible {
return None;
}
let dummy = crate::context::UiContext::new();
@@ -530,14 +556,15 @@ impl Element for MenuBar {
}
fn is_menu_open(&self) -> bool {
- self.visible && self.menus.iter().any(|m| m.is_menu_open())
+ self.base.visible && self.menus.iter().any(|m| m.is_menu_open())
}
fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
- if !self.visible {
+ if !self.base.visible {
return Vec::new();
}
let mut quads = Vec::new();
+ quads.extend(self.base.all_quads(ctx));
for menu in &self.menus {
quads.extend(menu.all_quads(ctx));
}
@@ -545,7 +572,7 @@ impl Element for MenuBar {
}
fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> {
- if !self.visible {
+ if !self.base.visible {
return Vec::new();
}
let mut arcs = Vec::new();
@@ -556,7 +583,7 @@ impl Element for MenuBar {
}
fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
- if !self.visible {
+ if !self.base.visible {
return;
}
let current_font = crate::layout::menubar_font();
@@ -574,7 +601,7 @@ impl Element for MenuBar {
let font_size = font_size_opt.unwrap_or(12.0);
if !self.title.is_empty() {
- if let Some((_ccx, _ccy, _ccr)) = self.curved_circle {
+ if let Some((_ccx, _ccy, _ccr)) = self.base.curved_circle {
if self.curved_title_char_bufs.len() != self.title.chars().count() {
let font_fam_clone = font_fam.clone();
self.curved_title_char_bufs = self.title.chars()
@@ -601,11 +628,17 @@ impl Element for MenuBar {
}
fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
- if !self.visible {
+ if !self.base.visible {
return Vec::new();
}
let mut items = Vec::new();
- let color = glyphon::Color::rgb(0xaa, 0xaa, 0xbb);
+ let label_color = crate::colors::paginator_tab_label_color();
+ let srgb = crate::colors::to_srgb(label_color);
+ let color = glyphon::Color::rgb(
+ (srgb[0] * 255.0) as u8,
+ (srgb[1] * 255.0) as u8,
+ (srgb[2] * 255.0) as u8,
+ );
let font_setting = crate::layout::menubar_font();
let (_, font_size_opt) = crate::layout::parse_font_string(&font_setting);
@@ -613,8 +646,8 @@ impl Element for MenuBar {
let char_w = 7.5 * (font_size / 12.0);
let padding_x = crate::layout::paginator_tab_padding_x();
- if let Some((ccx, ccy, ccr)) = self.curved_circle {
- let r_mid = ccr - self.h / 2.0;
+ if let Some((ccx, ccy, ccr)) = self.base.curved_circle {
+ let r_mid = ccr - self.base.base.h / 2.0;
let mut total_width = 8.0;
if !self.title.is_empty() {
total_width += self.title.len() as f32 * char_w + 24.0;
@@ -661,13 +694,13 @@ impl Element for MenuBar {
for menu in &self.menus {
total_width += menu.active_title().len() as f32 * char_w + 2.0 * padding_x;
}
- if self.w > total_width {
- start_x = (self.w - total_width) / 2.0;
+ if self.base.base.w > total_width {
+ start_x = (self.base.base.w - total_width) / 2.0;
}
}
if let Some(ref title_buf) = self.title_buf {
- let text_y = self.y + (self.h - font_size) / 2.0;
- items.push((title_buf, self.x + start_x, text_y, color));
+ let text_y = self.base.base.y + (self.base.base.h - font_size) / 2.0;
+ items.push((title_buf, self.base.base.x + start_x, text_y, color));
}
}
@@ -678,9 +711,16 @@ impl Element for MenuBar {
}
fn text_labels(&self) -> Vec<TextLabel> {
- if !self.visible {
+ if !self.base.visible {
return Vec::new();
}
+ let label_color = crate::colors::paginator_tab_label_color();
+ let srgb = crate::colors::to_srgb(label_color);
+ let text_color = [
+ (srgb[0] * 255.0) as u8,
+ (srgb[1] * 255.0) as u8,
+ (srgb[2] * 255.0) as u8,
+ ];
let padding_x = crate::layout::paginator_tab_padding_x();
let mut labels = Vec::new();
@@ -688,13 +728,11 @@ impl Element for MenuBar {
if self.vertical {
let font_size = 12.0;
let line_height = font_size * 1.2;
- let label_len = label.chars().count() as f32;
- let total_h = label_len * line_height;
- let start_y = self.y - (crate::layout::label_margin() + total_h);
+ let start_y = self.base.base.y + 8.0;
for (i, c) in label.chars().enumerate() {
let char_str = c.to_string();
let char_w = TextLabel::estimate_width(&char_str, font_size);
- let x_pos = self.x + (self.w - char_w) / 2.0;
+ let x_pos = self.base.base.x + (self.base.base.w - char_w) / 2.0;
let y_pos = start_y + i as f32 * line_height;
labels.push(TextLabel {
text: char_str,
@@ -704,14 +742,6 @@ impl Element for MenuBar {
color: [0x83, 0x83, 0x8a],
});
}
- } else {
- labels.push(TextLabel {
- text: label.clone(),
- x: self.x,
- y: self.y - (12.0 + crate::layout::label_margin()),
- font_size: 12.0,
- color: [0x83, 0x83, 0x8a],
- });
}
}
@@ -720,8 +750,8 @@ impl Element for MenuBar {
let font_size = font_size_opt.unwrap_or(12.0);
let char_w = 7.5 * (font_size / 12.0);
- if let Some((ccx, ccy, ccr)) = self.curved_circle {
- let r_mid = ccr - self.h / 2.0;
+ if let Some((ccx, ccy, ccr)) = self.base.curved_circle {
+ let r_mid = ccr - self.base.base.h / 2.0;
let mut total_width = 8.0;
if !self.title.is_empty() {
total_width += self.title.len() as f32 * char_w + 24.0;
@@ -741,24 +771,30 @@ impl Element for MenuBar {
ccx, ccy, r_mid,
current_angle, current_angle + dtheta_title,
font_size,
- [0xaa, 0xaa, 0xbb],
+ text_color,
));
}
} else if self.vertical {
if !self.title.is_empty() {
+ let mut start_y = self.base.base.y + 8.0;
+ if let Some(ref label) = self.label {
+ let font_size = 12.0;
+ let line_height = font_size * 1.2;
+ let label_h = label.chars().count() as f32 * line_height;
+ start_y += label_h + 8.0;
+ }
let line_height = font_size * 1.2;
- let start_y = self.y + 8.0;
+ let char_w = TextLabel::estimate_width("o", font_size);
+ let x_pos = self.base.base.x + (self.base.base.w - char_w) / 2.0;
for (i, c) in self.title.chars().enumerate() {
let char_str = c.to_string();
- let char_w = TextLabel::estimate_width(&char_str, font_size);
- let x_pos = self.x + (self.w - char_w) / 2.0;
let y_pos = start_y + i as f32 * line_height;
labels.push(TextLabel {
text: char_str,
x: x_pos,
y: y_pos,
font_size,
- color: [0xaa, 0xaa, 0xbb],
+ color: text_color,
});
}
}
@@ -772,18 +808,18 @@ impl Element for MenuBar {
for menu in &self.menus {
total_width += menu.active_title().len() as f32 * char_w + 2.0 * padding_x;
}
- if self.w > total_width {
- start_x = (self.w - total_width) / 2.0;
+ if self.base.base.w > total_width {
+ start_x = (self.base.base.w - total_width) / 2.0;
}
}
if !self.title.is_empty() {
- let text_y = self.y + (self.h - font_size) / 2.0;
+ let text_y = self.base.base.y + (self.base.base.h - font_size) / 2.0;
labels.push(TextLabel {
text: self.title.clone(),
- x: self.x + start_x,
+ x: self.base.base.x + start_x,
y: text_y,
font_size,
- color: [0xaa, 0xaa, 0xbb],
+ color: text_color,
});
}
}
@@ -794,17 +830,17 @@ impl Element for MenuBar {
}
fn set_visible(&mut self, visible: bool) {
- self.visible = visible;
+ self.base.set_visible(visible);
for menu in &mut self.menus {
menu.set_visible(visible);
}
}
fn visible(&self) -> bool {
- self.visible
+ self.base.visible()
}
- fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
+ fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
self.menus.iter().map(|m| {
let ptr: *const dyn Element = &**m as &dyn Element;
ptr as *mut (dyn Element + 'static)
@@ -826,9 +862,22 @@ impl Element for MenuBar {
fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> {
self.menu_dropdown_checked.clone()
}
-}
-impl Drop for MenuBar {
+ fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+ if !self.base.visible {
+ return Vec::new();
+ }
+ let mut quads = Vec::new();
+ for menu in &self.menus {
+ quads.extend(menu.extra_quads());
+ }
+ quads
+ }
+
+ fn widget_font(&self) -> Option<String> {
+ Some(crate::layout::menubar_font())
+ }
+}impl Drop for MenuBar {
fn drop(&mut self) {
focus::clear_if_matches(self);
}
@@ -853,6 +902,7 @@ pub struct Menu {
pub item_bufs: Vec<glyphon::Buffer>,
pub check_buf: Option<glyphon::Buffer>,
pub curved_char_bufs: Vec<glyphon::Buffer>,
+ pub font_family: String,
}
impl Menu {
@@ -875,6 +925,7 @@ impl Menu {
item_bufs: Vec::new(),
check_buf: None,
curved_char_bufs: Vec::new(),
+ font_family: String::new(),
}
}
@@ -922,8 +973,12 @@ impl Element for Menu {
[0.0, 0.0, 0.0, 0.0]
}
+ fn is_active(&self) -> bool {
+ self.base.focused || self.open
+ }
+
fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
@@ -1055,17 +1110,19 @@ impl Element for Menu {
self.open
}
- fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])>{
- if self.curved_arc.is_some() {
- None
- } else {
- let hc = self.highlight_color(ctx)?;
- Some((self.base.x, self.base.y, self.base.w, self.base.h, hc))
- }
+ fn highlight_quad(&self, _ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])>{
+ None
}
fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
let mut quads = Vec::new();
+ if self.curved_arc.is_none() {
+ if self.base.focused || self.open {
+ quads.push((self.base.x, self.base.y, self.base.w, self.base.h, colors::highlight_primary_color()));
+ } else if self.base.hovered {
+ quads.push((self.base.x, self.base.y, self.base.w, self.base.h, colors::HIGHLIGHT_SECONDARY));
+ }
+ }
if self.open {
let (dx, dy, dw, dh) = self.dropdown_rect();
if dh > 0.0 {
@@ -1089,6 +1146,13 @@ impl Element for Menu {
}
fn text_labels(&self) -> Vec<TextLabel> {
+ let label_color = crate::colors::paginator_tab_label_color();
+ let srgb = crate::colors::to_srgb(label_color);
+ let text_color = [
+ (srgb[0] * 255.0) as u8,
+ (srgb[1] * 255.0) as u8,
+ (srgb[2] * 255.0) as u8,
+ ];
let mut labels = Vec::new();
if let Some((cx, cy, r, thickness, start_angle, end_angle)) = self.curved_arc {
let r_mid = r - thickness / 2.0;
@@ -1097,7 +1161,7 @@ impl Element for Menu {
cx, cy, r_mid,
start_angle, end_angle,
12.0,
- [0xcc, 0xcc, 0xd4],
+ text_color,
));
} else if self.vertical {
let font_size = 12.0;
@@ -1106,17 +1170,17 @@ impl Element for Menu {
let label_len = title.chars().count() as f32;
let total_h = label_len * line_height;
let start_y = self.base.y + (self.base.h - total_h) / 2.0;
+ let char_w = TextLabel::estimate_width("o", font_size);
+ let x_pos = self.base.x + (self.base.w - char_w) / 2.0;
for (i, c) in title.chars().enumerate() {
let char_str = c.to_string();
- let char_w = TextLabel::estimate_width(&char_str, font_size);
- let x_pos = self.base.x + (self.base.w - char_w) / 2.0;
let y_pos = start_y + i as f32 * line_height;
labels.push(TextLabel {
text: char_str,
x: x_pos,
y: y_pos,
font_size,
- color: [0xcc, 0xcc, 0xd4],
+ color: text_color,
});
}
} else {
@@ -1125,7 +1189,7 @@ impl Element for Menu {
x: self.base.x + crate::layout::paginator_tab_padding_x(),
y: self.base.y + 7.0,
font_size: 12.0,
- color: [0xcc, 0xcc, 0xd4],
+ color: text_color,
});
}
if self.open {
@@ -1142,7 +1206,7 @@ impl Element for Menu {
x: dx + 8.0,
y: dy + i as f32 * DROPDOWN_ITEM_H + 5.0,
font_size: 12.0,
- color: [0xcc, 0xcc, 0xd4],
+ color: text_color,
});
}
}
@@ -1188,8 +1252,14 @@ impl Element for Menu {
}
fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
- let title_text = self.active_title();
let font_setting = crate::layout::menubar_font();
+ if self.font_family != font_setting {
+ self.font_family = font_setting.clone();
+ self.title_buf = None;
+ self.curved_char_bufs.clear();
+ self.item_bufs.clear();
+ }
+ let title_text = self.active_title();
let (font_fam, font_size_opt) = crate::layout::parse_font_string(&font_setting);
let font_size = font_size_opt.unwrap_or(12.0);
@@ -1232,7 +1302,13 @@ impl Element for Menu {
fn get_text_items(&self) -> Vec<(&glyphon::Buffer, f32, f32, glyphon::Color)> {
let mut items = Vec::new();
- let color = glyphon::Color::rgb(0xcc, 0xcc, 0xd4);
+ let label_color = crate::colors::paginator_tab_label_color();
+ let srgb = crate::colors::to_srgb(label_color);
+ let color = glyphon::Color::rgb(
+ (srgb[0] * 255.0) as u8,
+ (srgb[1] * 255.0) as u8,
+ (srgb[2] * 255.0) as u8,
+ );
let font_setting = crate::layout::menubar_font();
let (_, font_size_opt) = crate::layout::parse_font_string(&font_setting);
@@ -1285,6 +1361,10 @@ impl Element for Menu {
items
}
+
+ fn widget_font(&self) -> Option<String> {
+ Some(crate::layout::menubar_font())
+ }
}
unsafe impl Send for Menu {}
diff --git a/src/widget/container/paginator.rs b/src/widget/container/paginator.rs
index 1ce237f..d081b4a 100644
--- a/src/widget/container/paginator.rs
+++ b/src/widget/container/paginator.rs
@@ -238,6 +238,9 @@ impl Paginator {
}
pub fn set_pages(&mut self, pages: Vec<String>) {
+ if self.pages == pages {
+ return;
+ }
self.pages = pages.clone();
let num_pages = pages.len();
@@ -270,6 +273,57 @@ impl Paginator {
self.update_target_pos();
}
+ pub fn set_pages_with_items(&mut self, pages: Vec<String>, items: Vec<Vec<String>>) {
+ if self.pages == pages {
+ let mut items_changed = false;
+ if self.sidebar_menu.menus.len() == items.len() {
+ for (i, menu) in self.sidebar_menu.menus.iter().enumerate() {
+ if menu.items != items[i] {
+ items_changed = true;
+ break;
+ }
+ }
+ } else {
+ items_changed = true;
+ }
+ if !items_changed {
+ return;
+ }
+ }
+ self.pages = pages.clone();
+ let num_pages = pages.len();
+
+ self.update_sidebar_w();
+ let mut sidebar_menu = MenuBar::new(0.0, 0.0, self.sidebar_w, 0.0)
+ .with_vertical(true);
+ if let Some(ref l) = self.sidebar_label {
+ sidebar_menu = sidebar_menu.with_label(l);
+ }
+ for (i, page) in pages.iter().enumerate() {
+ let page_items: &[String] = if i < items.len() { &items[i] } else { &[] };
+ let page_items_ref: Vec<&str> = page_items.iter().map(|s| s.as_str()).collect();
+ sidebar_menu = sidebar_menu.with_item(page, &page_items_ref);
+ }
+ self.sidebar_menu = sidebar_menu;
+
+ let mut plates = Vec::new();
+ for _ in 0..num_pages {
+ let mut plate = Plate::new(0.0, 0.0, 0.0, 0.0).with_draggable(false);
+ plate.visible = false;
+ plates.push(plate);
+ }
+ self.plates = plates;
+ if self.selected_page >= num_pages {
+ self.selected_page = 0;
+ }
+ if !self.plates.is_empty() {
+ self.plates[self.selected_page].visible = true;
+ self.sidebar_menu.menus[self.selected_page].set_selected(true);
+ }
+ self.update_sidebar_w();
+ self.update_target_pos();
+ }
+
pub fn set_scale_factor(&mut self, scale: f32) {
self.scale_factor = scale;
}
@@ -543,6 +597,8 @@ impl Element for Paginator {
}
}
}
+ } else {
+ quads.extend(self.sidebar_menu.extra_quads());
}
quads
}
@@ -567,10 +623,10 @@ impl Element for Paginator {
let line_height = font_size * 1.2;
let sidebar_w = self.sidebar_w();
let start_y = self.y + 10.0;
+ let char_w = TextLabel::estimate_width("o", font_size);
+ let x_pos = self.x + (sidebar_w - char_w) / 2.0;
for (i, c) in label.chars().enumerate() {
let char_str = c.to_string();
- let char_w = TextLabel::estimate_width(&char_str, font_size);
- let x_pos = self.x + (sidebar_w - char_w) / 2.0;
let y_pos = start_y + i as f32 * line_height;
labels.push(TextLabel {
text: char_str,
@@ -647,11 +703,13 @@ impl Element for Paginator {
let mut changed = false;
let was_hovered_tab = self.hovered_tab;
self.hovered_tab = None;
- for i in 0..self.pages.len() {
- let (bx, by, bw, bh) = self.tab_rect(i);
- if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
- self.hovered_tab = Some(i);
- break;
+ if self.tabs_rotated {
+ for i in 0..self.pages.len() {
+ let (bx, by, bw, bh) = self.tab_rect(i);
+ if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
+ self.hovered_tab = Some(i);
+ break;
+ }
}
}
if was_hovered_tab != self.hovered_tab {
@@ -681,27 +739,68 @@ impl Element for Paginator {
let mut clicked_tab = false;
if button == MouseButton::Left {
- match state {
- ElementState::Pressed => {
- self.pressed_tab = None;
- for i in 0..self.pages.len() {
- let (bx, by, bw, bh) = self.tab_rect(i);
- if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
- self.pressed_tab = Some(i);
- clicked_tab = true;
- break;
+ if self.tabs_rotated {
+ match state {
+ ElementState::Pressed => {
+ self.pressed_tab = None;
+ for i in 0..self.pages.len() {
+ let (bx, by, bw, bh) = self.tab_rect(i);
+ if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
+ self.pressed_tab = Some(i);
+ clicked_tab = true;
+ break;
+ }
+ }
+ }
+ ElementState::Released => {
+ if let Some(i) = self.pressed_tab.take() {
+ let (bx, by, bw, bh) = self.tab_rect(i);
+ if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
+ if self.selected_page != i {
+ self.set_selected_page(i);
+ self.page_changed = true;
+ }
+ clicked_tab = true;
+ }
}
}
}
- ElementState::Released => {
- if let Some(i) = self.pressed_tab.take() {
- let (bx, by, bw, bh) = self.tab_rect(i);
- if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
+ } else {
+ if self.sidebar_menu.mouse_input(button, state, px, py, ctx) {
+ for i in 0..self.sidebar_menu.menus.len() {
+ if self.sidebar_menu.menus[i].is_menu_open() {
if self.selected_page != i {
self.set_selected_page(i);
self.page_changed = true;
}
- clicked_tab = true;
+ break;
+ }
+ }
+ clicked_tab = true;
+ } else {
+ match state {
+ ElementState::Pressed => {
+ self.pressed_tab = None;
+ for i in 0..self.sidebar_menu.menus.len() {
+ let (bx, by, bw, bh) = self.sidebar_menu.menus[i].rect();
+ if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
+ self.pressed_tab = Some(i);
+ if self.selected_page != i {
+ self.set_selected_page(i);
+ self.page_changed = true;
+ }
+ clicked_tab = true;
+ break;
+ }
+ }
+ }
+ ElementState::Released => {
+ if let Some(i) = self.pressed_tab.take() {
+ let (bx, by, bw, bh) = self.sidebar_menu.menus[i].rect();
+ if px >= bx && px <= bx + bw && py >= by && py <= by + bh && py >= self.y && py <= self.y + self.h {
+ clicked_tab = true;
+ }
+ }
}
}
}
@@ -712,10 +811,6 @@ impl Element for Paginator {
return true;
}
- if self.sidebar_menu.mouse_input(button, state, px, py, ctx) {
- return true;
- }
-
if self.selected_page < self.plates.len() {
if self.plates[self.selected_page].mouse_input(button, state, px, py, ctx) {
return true;
@@ -892,6 +987,9 @@ impl Element for Paginator {
fn set_pages(&mut self, pages: Vec<String>) {
self.set_pages(pages);
}
+ fn set_pages_with_items(&mut self, pages: Vec<String>, items: Vec<Vec<String>>) {
+ self.set_pages_with_items(pages, items);
+ }
fn sidebar_w(&self) -> f32 {
self.sidebar_w()
}
@@ -915,6 +1013,50 @@ impl Element for Paginator {
fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>> {
self.sidebar_menu.menu_checked_list()
}
+
+ fn get_menu_items_at(&self, px: f32, py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)> {
+ if self.sidebar_mode {
+ self.sidebar_menu.get_menu_items_at(px, py)
+ } else {
+ None
+ }
+ }
+
+ fn menu_click(&mut self) -> Option<(usize, usize)> {
+ if self.sidebar_mode {
+ self.sidebar_menu.menu_click()
+ } else {
+ None
+ }
+ }
+
+ fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
+ self.sidebar_menu.prepare_text(fs);
+ for plate in &mut self.plates {
+ plate.prepare_text(fs);
+ }
+ }
+
+ fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+ let mut result = Vec::new();
+ if self.tabs_rotated {
+ let font = self.widget_font();
+ for l in self.text_labels() {
+ result.push((l, font.clone(), None));
+ }
+ } else {
+ result.extend(self.sidebar_menu.text_labels_with_font_and_bounds(ctx));
+ }
+
+ if self.selected_page < self.plates.len() {
+ result.extend(self.plates[self.selected_page].text_labels_with_font_and_bounds(ctx));
+ }
+ result
+ }
+
+ fn widget_font(&self) -> Option<String> {
+ Some(crate::layout::menubar_font())
+ }
}
unsafe impl Send for Paginator {}
@@ -999,4 +1141,35 @@ mod tests {
crate::layout::set_paginator_tab_padding_y(orig_padding_y);
crate::layout::set_menubar_font(&orig_font);
}
+
+ #[test]
+ fn test_paginator_vertical_tabs() {
+ let pages = vec!["File".to_string(), "Edit".to_string()];
+ let mut paginator = Paginator::new(56.0, pages)
+ .with_tab_y_offset(10.0)
+ .with_tabs_rotated(false);
+
+ paginator.set_rect(0.0, 0.0, 1000.0, 600.0);
+
+ let mut dummy = crate::context::UiContext::new();
+
+ // Check rects of menus
+ let (bx, by, bw, bh) = paginator.sidebar_menu.menus[1].rect();
+ assert!(by > 0.0);
+
+ // Click Edit menu (index 1)
+ let px = bx + bw / 2.0;
+ let py = by + bh / 2.0;
+
+ // Press
+ let click_press = paginator.mouse_input(MouseButton::Left, ElementState::Pressed, px, py, &mut dummy);
+ assert!(click_press);
+ assert_eq!(paginator.selected_page, 1);
+
+ // Release
+ let click_release = paginator.mouse_input(MouseButton::Left, ElementState::Released, px, py, &mut dummy);
+ assert!(!click_release);
+ assert_eq!(paginator.selected_page, 1);
+ assert!(paginator.page_changed);
+ }
}
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 6e7c565..d493c2c 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -264,7 +264,7 @@ impl Element for ParametersBg {
if !self.visible {
return false;
}
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
px >= self.base.x && px <= self.base.x + self.base.w && py >= self.base.y && py <= self.base.y + self.base.h
diff --git a/src/widget/container/plate.rs b/src/widget/container/plate.rs
index 1b894bb..cfab8ef 100644
--- a/src/widget/container/plate.rs
+++ b/src/widget/container/plate.rs
@@ -95,6 +95,15 @@ impl Element for Plate {
self.visible
}
+ fn set_visible(&mut self, visible: bool) {
+ self.visible = visible;
+ for &child_ptr in &self.children {
+ unsafe {
+ (*child_ptr).set_visible(visible);
+ }
+ }
+ }
+
fn color(&self) -> [f32; 4] {
let mut c = if let Some(c) = self.color {
c
@@ -121,7 +130,7 @@ impl Element for Plate {
}
fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
if let Some((cx, cy, r)) = self.curved_circle {
@@ -368,6 +377,43 @@ impl Element for Plate {
result
}
+ fn text_labels_with_font_and_bounds(&self, ctx: &UiContext) -> Vec<(TextLabel, Option<String>, Option<[f32; 4]>)> {
+ if !self.visible {
+ return Vec::new();
+ }
+ let mut result = Vec::new();
+ if let Some(ref label) = self.base.label {
+ result.push((
+ TextLabel {
+ text: label.clone(),
+ x: self.base.x,
+ y: self.base.y - (12.0 + crate::layout::label_margin()),
+ font_size: 12.0,
+ color: [0x83, 0x83, 0x8a],
+ },
+ None,
+ None,
+ ));
+ }
+ for &child_ptr in &self.children {
+ let widget = unsafe { &*child_ptr };
+ result.extend(widget.text_labels_with_font_and_bounds(ctx));
+ }
+ result
+ }
+
+ fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
+ if !self.visible {
+ return;
+ }
+ for &child_ptr in &self.children {
+ unsafe {
+ (*child_ptr).prepare_text(fs);
+ }
+ }
+ }
+
+
fn on_cursor_moved(&mut self, px: f32, py: f32, ctx: &mut UiContext) -> bool {
if !self.visible {
return false;
diff --git a/src/widget/container/spreadsheet.rs b/src/widget/container/spreadsheet.rs
index 2b7a0db..22e7957 100644
--- a/src/widget/container/spreadsheet.rs
+++ b/src/widget/container/spreadsheet.rs
@@ -76,7 +76,7 @@ impl Element for Spreadsheet {
if !self.visible {
return false;
}
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
let (rx, ry, rw, rh) = self.rect();
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index ebf9469..7a394fd 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -147,7 +147,7 @@ impl Element for Graph {
fn set_hovered(&mut self, v: bool) { self.hovered = v; }
fn hovered(&self) -> bool { self.hovered }
fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
px >= self.x && px < self.x + self.w && py >= self.y && py < self.y + self.h
diff --git a/src/widget/display/text_label.rs b/src/widget/display/text_label.rs
index a053cb5..ef18314 100644
--- a/src/widget/display/text_label.rs
+++ b/src/widget/display/text_label.rs
@@ -78,11 +78,12 @@ pub(crate) fn make_widget_text_buffer(fs: &mut glyphon::FontSystem, text: &str,
let physical_size = size * scale;
let metrics = glyphon::Metrics::new(physical_size, physical_size * 1.4);
let mut buf = glyphon::Buffer::new(fs, metrics);
- let family = match font_family {
+ let (family_name, _) = crate::layout::parse_font_string(font_family);
+ let family = match family_name.as_str() {
"monospace" => glyphon::Family::Monospace,
"sans-serif" => glyphon::Family::SansSerif,
"serif" => glyphon::Family::Serif,
- name => glyphon::Family::Name(name),
+ _ => glyphon::Family::Name(&family_name),
};
let attrs = glyphon::Attrs::new().family(family);
buf.set_text(fs, text, attrs, glyphon::Shaping::Advanced);
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 950db49..007bfc6 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -135,7 +135,7 @@ impl Element for Dropdown {
}
fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
- if crate::widget::popovers::is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
+ if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
return false;
}
let (x, y, w, h) = self.rect();
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 725e14d..bc1ef2f 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -481,6 +481,7 @@ pub trait Element {
fn is_page_hidden(&self) -> bool { false }
fn set_page_hidden(&mut self, _hidden: bool) {}
fn set_pages(&mut self, _pages: Vec<String>) {}
+ fn set_pages_with_items(&mut self, _pages: Vec<String>, _items: Vec<Vec<String>>) {}
fn sidebar_w(&self) -> f32 { 0.0 }
fn set_sidebar_mode(&mut self, _enabled: bool) {}
fn set_sidebar_label(&mut self, _label: Option<String>) {}