git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit235268c5985b284ed4a4bf749e7e783024fee544
parent180af574fc
authorLucas Galante <[email protected]>
date2026-06-09 15:11
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>) {}