diff --git a/examples/reactive_layout_demo/src/main.rs b/examples/reactive_layout_demo/src/main.rs index f725a72..aad2c25 100644 --- a/examples/reactive_layout_demo/src/main.rs +++ b/examples/reactive_layout_demo/src/main.rs @@ -101,6 +101,7 @@ async fn run_demo() -> Result<(), Box> { window.surface_target(), initial_size.width as u32, initial_size.height as u32, + ruin_ui::Color::rgb(0x08, 0x0C, 0x14), ) .expect("wgpu renderer should initialize"); let state = Rc::new(RefCell::new(WorkerState { diff --git a/examples/wayland_wgpu_demo/src/main.rs b/examples/wayland_wgpu_demo/src/main.rs index 1185ce0..0069f4d 100644 --- a/examples/wayland_wgpu_demo/src/main.rs +++ b/examples/wayland_wgpu_demo/src/main.rs @@ -36,6 +36,7 @@ fn main() -> Result<(), Box> { window.surface_target(), scene.logical_size.width as u32, scene.logical_size.height as u32, + ruin_ui::Color::rgb(0x08, 0x0C, 0x14), )?; println!("Opening RUIN Wayland / wgpu demo window..."); diff --git a/lib/ruin_app/src/lib.rs b/lib/ruin_app/src/lib.rs index f52af66..7eafeae 100644 --- a/lib/ruin_app/src/lib.rs +++ b/lib/ruin_app/src/lib.rs @@ -58,6 +58,11 @@ impl Window { self.spec = self.spec.requested_inner_size(UiSize::new(width, height)); self } + + pub fn base_color(mut self, color: Color) -> Self { + self.spec = self.spec.base_color(color); + self + } } impl Default for Window { @@ -774,7 +779,7 @@ pub mod surfaces { pub fn column() -> ContainerBuilder { ContainerBuilder { - element: Element::column().background(surfaces::canvas()), + element: Element::column(), widget_ref: None, } } diff --git a/lib/ui/src/window.rs b/lib/ui/src/window.rs index 96a09cb..9072de7 100644 --- a/lib/ui/src/window.rs +++ b/lib/ui/src/window.rs @@ -1,6 +1,6 @@ //! Common window model types. -use crate::scene::UiSize; +use crate::scene::{Color, UiSize}; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId(u64); @@ -55,6 +55,7 @@ pub struct WindowSpec { pub maximized: bool, pub fullscreen: bool, pub resizable: bool, + pub base_color: Color, } impl WindowSpec { @@ -72,9 +73,15 @@ impl WindowSpec { maximized: false, fullscreen: false, resizable: true, + base_color: Color::rgb(0x08, 0x0C, 0x14), } } + pub fn base_color(mut self, color: Color) -> Self { + self.base_color = color; + self + } + pub fn key(mut self, key: impl Into) -> Self { self.key = Some(key.into()); self diff --git a/lib/ui_platform_wayland/src/lib.rs b/lib/ui_platform_wayland/src/lib.rs index 030e0ff..30ce8b8 100644 --- a/lib/ui_platform_wayland/src/lib.rs +++ b/lib/ui_platform_wayland/src/lib.rs @@ -1096,6 +1096,7 @@ fn spawn_window_worker( spec.requested_inner_size .unwrap_or_else(|| UiSize::new(800.0, 500.0)) .height as u32, + spec.base_color, ) { Ok(renderer) => renderer, Err(_) => { diff --git a/lib/ui_renderer_wgpu/src/lib.rs b/lib/ui_renderer_wgpu/src/lib.rs index 08edc27..aa34238 100644 --- a/lib/ui_renderer_wgpu/src/lib.rs +++ b/lib/ui_renderer_wgpu/src/lib.rs @@ -330,6 +330,7 @@ pub struct WgpuSceneRenderer { image_cache_order: VecDeque, #[allow(dead_code)] glyph_atlas: GlyphAtlas, + base_color: Color, } const MAX_TEXT_CACHE_ENTRIES: usize = 64; @@ -343,6 +344,7 @@ impl WgpuSceneRenderer { target: impl HasDisplayHandle + HasWindowHandle + Send + Sync + 'static, width: u32, height: u32, + base_color: Color, ) -> Result> { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle()); let surface = instance.create_surface(target)?; @@ -361,6 +363,15 @@ impl WgpuSceneRenderer { .copied() .find(wgpu::TextureFormat::is_srgb) .unwrap_or(caps.formats[0]); + let alpha_mode = if base_color.a < 255 + && caps + .alpha_modes + .contains(&wgpu::CompositeAlphaMode::PreMultiplied) + { + wgpu::CompositeAlphaMode::PreMultiplied + } else { + caps.alpha_modes[0] + }; let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format, @@ -368,7 +379,7 @@ impl WgpuSceneRenderer { height: height.max(1), present_mode: wgpu::PresentMode::AutoVsync, desired_maximum_frame_latency: 2, - alpha_mode: caps.alpha_modes[0], + alpha_mode, view_formats: vec![], }; surface.configure(&device, &config); @@ -415,7 +426,7 @@ fn sd_round_rect(point: vec2, rect: vec4, radius: f32) -> f32 { fn rounded_rect_alpha(point: vec2, rect: vec4, radius: f32) -> f32 { let distance = sd_round_rect(point, rect, radius); - return 1.0 - smoothstep(0.0, 1.0, distance); + return 1.0 - smoothstep(-0.5, 0.5, distance); } fn blurred_round_rect_alpha(point: vec2, rect: vec4, radius: f32, blur: f32) -> f32 { @@ -553,7 +564,7 @@ fn sd_round_rect(point: vec2, rect: vec4, radius: f32) -> f32 { fn rounded_rect_alpha(point: vec2, rect: vec4, radius: f32) -> f32 { let distance = sd_round_rect(point, rect, radius); - return 1.0 - smoothstep(0.0, 1.0, distance); + return 1.0 - smoothstep(-0.5, 0.5, distance); } fn apply_clip_alpha(point: vec2, clip_rect: vec4, rounded_clip_rect: vec4, clip_params: vec2) -> f32 { @@ -710,6 +721,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4 { image_cache: HashMap::new(), image_cache_order: VecDeque::new(), glyph_atlas, + base_color, }) } @@ -792,11 +804,16 @@ fn fs_main(input: VertexOut) -> @location(0) vec4 { depth_slice: None, resolve_target: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.03, - g: 0.04, - b: 0.08, - a: 1.0, + load: wgpu::LoadOp::Clear({ + let a = self.base_color.a as f64 / 255.0; + // Premultiplied: multiply RGB by alpha so the compositor + // can blend correctly when alpha_mode is PreMultiplied. + wgpu::Color { + r: (self.base_color.r as f64 / 255.0) * a, + g: (self.base_color.g as f64 / 255.0) * a, + b: (self.base_color.b as f64 / 255.0) * a, + a, + } }), store: wgpu::StoreOp::Store, },