From 90bdf99e32c2409bf338a8914115b50d868bd115 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:43:16 +0900 Subject: [PATCH] text shadow + battery display poc --- wgui/doc/widgets.md | 10 +++++++ wgui/src/drawing.rs | 4 +-- wgui/src/parser/style.rs | 19 ++++++++++++++ wgui/src/renderer_vk/context.rs | 23 +++++++++++++--- wgui/src/renderer_vk/text/mod.rs | 26 ++++++++++++++++++- wgui/src/renderer_vk/text/text_renderer.rs | 15 ++++++----- wgui/src/widget/label.rs | 3 ++- wgui/src/widget/sprite.rs | 3 ++- wlx-overlay-s/src/assets/gui/watch.xml | 21 ++++++++------- wlx-overlay-s/src/gui/panel/label.rs | 5 +--- .../src/overlays/keyboard/builder.rs | 8 +++--- 11 files changed, 105 insertions(+), 32 deletions(-) diff --git a/wgui/doc/widgets.md b/wgui/doc/widgets.md index de92d88..e2c95d8 100644 --- a/wgui/doc/widgets.md +++ b/wgui/doc/widgets.md @@ -100,6 +100,16 @@ _Text size in pixel units_ `weight`: "normal" | "bold" +`shadow`: #112233 | #112233CC (default: None) + +`shadow_x`: **float** (default: 1.5) + +_Horizontal offset of the shadow from the original text. Positive is right._ + +`shadow_y`: **float** (default: 1.5) + +_Vertical offset of the shadow from the original text. Positive is down._ + --- ## rectangle widget diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 045275c..fc15dc8 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -7,7 +7,7 @@ use taffy::TraversePartialTree; use crate::{ drawing, layout::Widget, - renderer_vk::text::custom_glyph::CustomGlyph, + renderer_vk::text::{custom_glyph::CustomGlyph, TextShadow}, stack::{self, ScissorStack, TransformStack}, widget::{self}, }; @@ -135,7 +135,7 @@ pub struct PrimitiveExtent { pub enum RenderPrimitive { Rectangle(PrimitiveExtent, Rectangle), - Text(PrimitiveExtent, Rc>), + Text(PrimitiveExtent, Rc>, Option), Sprite(PrimitiveExtent, Option), //option because we want as_slice ScissorEnable(Boundary), ScissorDisable, diff --git a/wgui/src/parser/style.rs b/wgui/src/parser/style.rs index 234190e..1018c28 100644 --- a/wgui/src/parser/style.rs +++ b/wgui/src/parser/style.rs @@ -78,6 +78,25 @@ pub fn parse_text_style(attribs: &[AttribPair]) -> TextStyle { print_invalid_attrib(key, value); } } + "shadow" => { + if let Some(color) = parse_color_hex(value) { + style.shadow.get_or_insert_default().color = color; + } + } + "shadow_x" => { + if let Ok(x) = value.parse::() { + style.shadow.get_or_insert_default().x = x; + } else { + print_invalid_attrib(key, value); + } + } + "shadow_y" => { + if let Ok(y) = value.parse::() { + style.shadow.get_or_insert_default().y = y; + } else { + print_invalid_attrib(key, value); + } + } _ => {} } } diff --git a/wgui/src/renderer_vk/context.rs b/wgui/src/renderer_vk/context.rs index d4b084b..8fa66f0 100644 --- a/wgui/src/renderer_vk/context.rs +++ b/wgui/src/renderer_vk/context.rs @@ -2,20 +2,20 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use cosmic_text::Buffer; use glam::{Mat4, Vec2, Vec3}; -use slotmap::{SlotMap, new_key_type}; +use slotmap::{new_key_type, SlotMap}; use vulkano::pipeline::graphics::viewport; use crate::{ drawing::{self}, - gfx::{WGfx, cmd::GfxCommandBuffer}, + gfx::{cmd::GfxCommandBuffer, WGfx}, }; use super::{ rect::{RectPipeline, RectRenderer}, text::{ - DEFAULT_METRICS, FONT_SYSTEM, SWASH_CACHE, TextArea, TextBounds, text_atlas::{TextAtlas, TextPipeline}, text_renderer::TextRenderer, + TextArea, TextBounds, DEFAULT_METRICS, FONT_SYSTEM, SWASH_CACHE, }, viewport::Viewport, }; @@ -248,7 +248,20 @@ impl Context { .rect_renderer .add_rect(extent.boundary, *rectangle, &extent.transform); } - drawing::RenderPrimitive::Text(extent, text) => { + drawing::RenderPrimitive::Text(extent, text, shadow) => { + if let Some(shadow) = shadow { + pass.text_areas.push(TextArea { + buffer: text.clone(), + left: (extent.boundary.pos.x + shadow.x) * self.pixel_scale, + top: (extent.boundary.pos.y + shadow.y) * self.pixel_scale, + bounds: TextBounds::default(), //FIXME: just using boundary coords here doesn't work + scale: self.pixel_scale, + default_color: cosmic_text::Color::rgb(0, 0, 0), + override_color: Some(shadow.color.into()), + custom_glyphs: &[], + transform: extent.transform, + }); + } pass.text_areas.push(TextArea { buffer: text.clone(), left: extent.boundary.pos.x * self.pixel_scale, @@ -256,6 +269,7 @@ impl Context { bounds: TextBounds::default(), //FIXME: just using boundary coords here doesn't work scale: self.pixel_scale, default_color: cosmic_text::Color::rgb(0, 0, 0), + override_color: None, custom_glyphs: &[], transform: extent.transform, }); @@ -269,6 +283,7 @@ impl Context { scale: self.pixel_scale, custom_glyphs: sprites.as_slice(), default_color: cosmic_text::Color::rgb(255, 0, 255), + override_color: None, transform: extent.transform, }); } diff --git a/wgui/src/renderer_vk/text/mod.rs b/wgui/src/renderer_vk/text/mod.rs index 4892333..4d2ea33 100644 --- a/wgui/src/renderer_vk/text/mod.rs +++ b/wgui/src/renderer_vk/text/mod.rs @@ -25,6 +25,23 @@ const DEFAULT_LINE_HEIGHT_RATIO: f32 = 1.43; pub(crate) const DEFAULT_METRICS: Metrics = Metrics::new(DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE * DEFAULT_LINE_HEIGHT_RATIO); +#[derive(Clone)] +pub struct TextShadow { + pub y: f32, + pub x: f32, + pub color: drawing::Color, +} + +impl Default for TextShadow { + fn default() -> Self { + Self { + y: 1.5, + x: 1.5, + color: drawing::Color::default(), + } + } +} + #[derive(Default, Clone)] pub struct TextStyle { pub size: Option, @@ -34,6 +51,7 @@ pub struct TextStyle { pub weight: Option, pub align: Option, pub wrap: bool, + pub shadow: Option, } impl From<&TextStyle> for Attrs<'_> { @@ -60,7 +78,11 @@ impl From<&TextStyle> for Metrics { impl From<&TextStyle> for Wrap { fn from(value: &TextStyle) -> Self { - if value.wrap { Self::WordOrGlyph } else { Self::None } + if value.wrap { + Self::WordOrGlyph + } else { + Self::None + } } } @@ -199,6 +221,8 @@ pub struct TextArea<'a> { pub bounds: TextBounds, /// The default color of the text area. pub default_color: Color, + /// Override text color. Used for shadow. + pub override_color: Option, /// Additional custom glyphs to render. pub custom_glyphs: &'a [CustomGlyph], /// Text transformation diff --git a/wgui/src/renderer_vk/text/text_renderer.rs b/wgui/src/renderer_vk/text/text_renderer.rs index d30eb6d..1f485e2 100644 --- a/wgui/src/renderer_vk/text/text_renderer.rs +++ b/wgui/src/renderer_vk/text/text_renderer.rs @@ -4,9 +4,9 @@ use crate::{ }; use super::{ - ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea, custom_glyph::{CustomGlyphCacheKey, RasterizeCustomGlyphRequest, RasterizedCustomGlyph}, text_atlas::{GlyphVertex, TextAtlas, TextPipeline}, + ContentType, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, TextArea, }; use cosmic_text::{Color, SubpixelBin, SwashContent}; use glam::{Mat4, Vec2, Vec3}; @@ -96,7 +96,10 @@ impl TextRenderer { y_bin, }); - let color = glyph.color.unwrap_or(text_area.default_color); + let color = text_area + .override_color + .or(glyph.color) + .unwrap_or(text_area.default_color); if let Some(glyph_to_render) = prepare_glyph( &mut PrepareGlyphParams { @@ -168,10 +171,10 @@ impl TextRenderer { for glyph in run.glyphs { let physical_glyph = glyph.physical((text_area.left, text_area.top), text_area.scale); - let color = match glyph.color_opt { - Some(some) => some, - None => text_area.default_color, - }; + let color = text_area + .override_color + .or(glyph.color_opt) + .unwrap_or(text_area.default_color); if let Some(glyph_to_render) = prepare_glyph( &mut PrepareGlyphParams { diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index 093caec..8518cf2 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -10,7 +10,7 @@ use crate::{ globals::Globals, i18n::{I18n, Translation}, layout::WidgetID, - renderer_vk::text::{FONT_SYSTEM, TextStyle}, + renderer_vk::text::{TextStyle, FONT_SYSTEM}, }; use super::{WidgetObj, WidgetState}; @@ -124,6 +124,7 @@ impl WidgetObj for WidgetLabel { transform: state.transform_stack.get().transform, }, self.buffer.clone(), + self.params.style.shadow.clone(), )); } diff --git a/wgui/src/widget/sprite.rs b/wgui/src/widget/sprite.rs index 60f76e0..f150c98 100644 --- a/wgui/src/widget/sprite.rs +++ b/wgui/src/widget/sprite.rs @@ -7,8 +7,8 @@ use crate::{ drawing::{self, PrimitiveExtent}, layout::WidgetID, renderer_vk::text::{ - DEFAULT_METRICS, FONT_SYSTEM, custom_glyph::{CustomGlyph, CustomGlyphData}, + DEFAULT_METRICS, FONT_SYSTEM, }, }; @@ -81,6 +81,7 @@ impl WidgetObj for WidgetSprite { transform: state.transform_stack.get().transform, }, Rc::new(RefCell::new(buffer)), + None, )); } } diff --git a/wlx-overlay-s/src/assets/gui/watch.xml b/wlx-overlay-s/src/assets/gui/watch.xml index 79f9af8..c021ce0 100644 --- a/wlx-overlay-s/src/assets/gui/watch.xml +++ b/wlx-overlay-s/src/assets/gui/watch.xml @@ -20,13 +20,16 @@ @@ -35,12 +38,12 @@
- - - - - - + + + + + +
@@ -74,4 +77,4 @@
- \ No newline at end of file + diff --git a/wlx-overlay-s/src/gui/panel/label.rs b/wlx-overlay-s/src/gui/panel/label.rs index caf72ec..461d9f1 100644 --- a/wlx-overlay-s/src/gui/panel/label.rs +++ b/wlx-overlay-s/src/gui/panel/label.rs @@ -376,16 +376,13 @@ fn battery_on_tick( app: &AppState, ) { let device = app.input_state.devices.get(state.device); - - let tags = ["", "H", "L", "R", "T"]; - let label = data.obj.get_as_mut::().unwrap(); if let Some(device) = device && let Some(soc) = device.soc { let soc = (soc * 100.).min(99.) as u32; - let text = format!("{}{}", tags[device.role as usize], soc); + let text = soc.to_string(); let color = if device.charging { state.charging_color } else if soc < state.low_threshold { diff --git a/wlx-overlay-s/src/overlays/keyboard/builder.rs b/wlx-overlay-s/src/overlays/keyboard/builder.rs index e68ba0f..05bd33b 100644 --- a/wlx-overlay-s/src/overlays/keyboard/builder.rs +++ b/wlx-overlay-s/src/overlays/keyboard/builder.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, rc::Rc}; -use glam::{Mat4, Vec2, Vec3, vec2, vec3a}; +use glam::{vec2, vec3a, Mat4, Vec2, Vec3}; use wgui::{ animation::{Animation, AnimationEasing}, drawing::Color, @@ -20,13 +20,13 @@ use crate::{ backend::overlay::{OverlayData, OverlayState, Positioning}, gui::panel::GuiPanel, state::AppState, - subsystem::hid::{ALT, CTRL, META, SHIFT, SUPER, XkbKeymap}, + subsystem::hid::{XkbKeymap, ALT, CTRL, META, SHIFT, SUPER}, }; use super::{ - KEYBOARD_NAME, KeyButtonData, KeyState, KeyboardBackend, KeyboardState, handle_press, - handle_release, + handle_press, handle_release, layout::{self, AltModifier, KeyCapType}, + KeyButtonData, KeyState, KeyboardBackend, KeyboardState, KEYBOARD_NAME, }; const BACKGROUND_PADDING: f32 = 4.;