GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: cce-bevel transparency dialog
An Opacity button beside Save/Reset toggles a small dialog — a raised
plate floated over the cutaway, its own alpha pinned readable — holding
one band slider that drives the window plate's alpha live (the popup
becomes its own glass sample). Escape and press-outside dismiss, ahead
of the Escape-exits default; presses on the toggle button fall through
so it can close the dialog itself.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/bin/cce-bevel.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 100 insertions(+), 11 deletions(-)
diff --git a/src/bin/cce-bevel.rs b/src/bin/cce-bevel.rs
index 8516a40..2362a42 100644
--- a/src/bin/cce-bevel.rs
+++ b/src/bin/cce-bevel.rs
@@ -149,6 +149,14 @@ struct BevelPopup {
width_slider: Adapted<Slider>,
save_button: Adapted<Button>,
reset_button: Adapted<Button>,
+ /// The transparency dialog: a raised plate floated over the popup with
+ /// one slider driving the window plate's alpha, toggled by its button.
+ opacity_button: Adapted<Button>,
+ opacity_slider: Adapted<Slider>,
+ opacity_open: bool,
+ plate_opacity: f32,
+ dialog_rect: Rect,
+ opacity_btn_rect: Rect,
/// Status line under the buttons: what the last save/reset did.
status: String,
ui_context: cce_ui::context::UiContext,
@@ -309,8 +317,10 @@ fn draw_section(pc: &mut PaintCtx, rect: Rect, profile: &ProfileKnobs, has_floor
}
impl BevelPopup {
- fn root_ids(&self) -> [WidgetId; 11] {
+ fn root_ids(&self) -> [WidgetId; 13] {
[
+ self.opacity_slider.id(),
+ self.opacity_button.id(),
self.profile_dropdown.id(),
self.wall.shoulder.id(),
self.wall.base.id(),
@@ -325,8 +335,10 @@ impl BevelPopup {
]
}
- fn roots(&mut self) -> [*mut (dyn WidgetHost + 'static); 11] {
+ fn roots(&mut self) -> [*mut (dyn WidgetHost + 'static); 13] {
[
+ self.opacity_slider.as_ptr_mut(),
+ self.opacity_button.as_ptr_mut(),
self.profile_dropdown.as_ptr_mut(),
self.wall.shoulder.as_ptr_mut(),
self.wall.base.as_ptr_mut(),
@@ -391,6 +403,15 @@ impl BevelPopup {
self.reset_live();
self.needs_rebuild = true;
}
+ if self.opacity_button.take_click() {
+ self.opacity_open = !self.opacity_open;
+ self.needs_rebuild = true;
+ }
+ if self.opacity_slider.take_change() {
+ self.plate_opacity = self.opacity_slider.inner().value();
+ println!("opacity {:.2}", self.plate_opacity);
+ self.needs_rebuild = true;
+ }
}
/// Persist the current material to the shared config
@@ -496,6 +517,18 @@ impl Application for BevelPopup {
.with_band(true),
save_button: Button::new(0.0, 0.0, 0.0, 0.0).with_label("Save"),
reset_button: Button::new(0.0, 0.0, 0.0, 0.0).with_label("Reset"),
+ opacity_button: Button::new(0.0, 0.0, 0.0, 0.0).with_label("Opacity"),
+ opacity_slider: Slider::new()
+ .with_label("Opacity")
+ .with_value(cce_ui::color::active_backplate_opacity().clamp(0.0, 1.0))
+ .with_readout(true)
+ .with_decimals(2)
+ .with_scroll(true)
+ .with_band(true),
+ opacity_open: false,
+ plate_opacity: cce_ui::color::active_backplate_opacity(),
+ dialog_rect: Rect::ZERO,
+ opacity_btn_rect: Rect::ZERO,
status: "Edits apply live; Save writes config.kdl.".to_string(),
ui_context: cce_ui::context::UiContext::new(),
width: 520,
@@ -611,9 +644,35 @@ impl Application for BevelPopup {
y += knob_h + gap;
self.save_button.set_rect(x, y, 96.0, button_h);
self.reset_button.set_rect(x + 96.0 + 12.0, y, 96.0, button_h);
+ self.opacity_btn_rect = Rect { x: x + 2.0 * (96.0 + 12.0), y, width: 96.0, height: button_h };
+ self.opacity_button.set_rect(
+ self.opacity_btn_rect.x,
+ self.opacity_btn_rect.y,
+ self.opacity_btn_rect.width,
+ self.opacity_btn_rect.height,
+ );
y += button_h + 8.0;
self.status_pos = (x, y);
+ // The transparency dialog floats centered over the cutaway; its
+ // slider parks off-screen while closed.
+ self.dialog_rect = Rect {
+ x: x + (w - 300.0).max(0.0) / 2.0,
+ y: self.cut_rect.y + (self.cut_rect.height - 92.0).max(0.0) / 2.0,
+ width: 300.0f32.min(w),
+ height: 92.0,
+ };
+ if self.opacity_open {
+ self.opacity_slider.set_rect(
+ self.dialog_rect.x + 18.0,
+ self.dialog_rect.y + (92.0 - knob_h) / 2.0 + 4.0,
+ self.dialog_rect.width - 36.0,
+ knob_h,
+ );
+ } else {
+ self.opacity_slider.set_rect(-1000.0, -1000.0, 0.0, 0.0);
+ }
+
self.needs_rebuild = false;
self.ui_context.rebuild_spatial_grid();
}
@@ -630,9 +689,7 @@ impl Application for BevelPopup {
// previews the edge profile, and the wells/buttons preview the wall
// profile — the popup is its own material sample.
let mut plate = cce_ui::color::page_low_color();
- if plate[3] > 0.001 {
- plate[3] = cce_ui::color::active_backplate_opacity();
- }
+ plate[3] = self.plate_opacity;
let radius = cce_ui::colors::backplate_corner_radius();
let bevel = cce_ui::layout::bevel_width();
pc.plate(
@@ -669,6 +726,17 @@ impl Application for BevelPopup {
cce_ui::scene::painter::paint_root_into(&self.ui_context, &self.profile_dropdown, &mut pc);
cce_ui::scene::painter::paint_root_into(&self.ui_context, &self.save_button, &mut pc);
cce_ui::scene::painter::paint_root_into(&self.ui_context, &self.reset_button, &mut pc);
+ cce_ui::scene::painter::paint_root_into(&self.ui_context, &self.opacity_button, &mut pc);
+
+ // The transparency dialog: a raised plate floated over the cutaway,
+ // its own alpha pinned readable so the slider stays usable at glassy
+ // window settings.
+ if self.opacity_open {
+ let mut dc = cce_ui::color::page_low_color();
+ dc[3] = 0.96;
+ pc.plate(self.dialog_rect, (10.0, 10.0, 10.0, 10.0), dc, bevel * 0.75);
+ cce_ui::scene::painter::paint_root_into(&self.ui_context, &self.opacity_slider, &mut pc);
+ }
// The selector's popover, drawn into the frame on top of everything
// below it (its labels carry the popover rect as bounds).
@@ -758,6 +826,21 @@ impl Application for BevelPopup {
self.needs_rebuild = true;
return None;
}
+ // A press outside the open transparency dialog dismisses it (presses
+ // on its toggle button fall through — the button click handles it).
+ if self.opacity_open && state == ElementState::Pressed {
+ let d = self.dialog_rect;
+ let b = self.opacity_btn_rect;
+ let inside = |r: Rect| {
+ pos.x >= r.x && pos.x <= r.x + r.width && pos.y >= r.y && pos.y <= r.y + r.height
+ };
+ if !inside(d) && !inside(b) {
+ self.opacity_open = false;
+ *needs_rebuild = true;
+ self.needs_rebuild = true;
+ return None;
+ }
+ }
let ev = Event::MouseButton {
button,
state,
@@ -813,12 +896,18 @@ impl Application for BevelPopup {
) -> Option<Self::Message> {
use cce_ui::widget::{Key, NamedKey};
if event.state == ElementState::Pressed {
- // Escape exits — unless the context menu is up (the toolkit-wide
- // Escape-dismiss should win the first press).
- if event.logical_key == Key::Named(NamedKey::Escape)
- && !self.ui_context.is_context_menu_visible()
- {
- return Some(BevelMsg::Exit);
+ // Escape closes the transparency dialog first, then the context
+ // menu (toolkit-wide default), and only then exits.
+ if event.logical_key == Key::Named(NamedKey::Escape) {
+ if self.opacity_open {
+ self.opacity_open = false;
+ *needs_rebuild = true;
+ self.needs_rebuild = true;
+ return None;
+ }
+ if !self.ui_context.is_context_menu_visible() {
+ return Some(BevelMsg::Exit);
+ }
}
if event.ctrl {
if let Key::Character(ref c) = event.logical_key {