From 041023ab20a0c1af9c7dcb7209ba4ce7fe903bda Mon Sep 17 00:00:00 2001 From: JoeZane Date: Sat, 26 Apr 2025 03:42:26 +0800 Subject: [PATCH] upd: add aligns to cell render, fix radius border widget rendering error with no radius child. --- examples/input_element/popup.rs | 4 +- examples/list_view/list_view_holder.rs | 3 +- examples/list_view/main.rs | 24 +++- tmui/src/application_window.rs | 25 +++- tmui/src/graphics/painter.rs | 11 ++ tmui/src/icons/svg_icon.rs | 2 +- tmui/src/icons/svg_toggle_icon.rs | 2 +- tmui/src/input/number.rs | 4 +- tmui/src/input/select/mod.rs | 2 +- tmui/src/views/cell/cell_render.rs | 134 +++++++++++++++++++- tmui/src/views/list_view/list_node.rs | 29 +++-- tmui/src/views/list_view/list_view_image.rs | 4 + tmui/src/views/list_view/mod.rs | 3 +- tmui/src/widget/mod.rs | 24 ++-- tmui/src/widget/widget_ext.rs | 18 +++ 15 files changed, 253 insertions(+), 36 deletions(-) diff --git a/examples/input_element/popup.rs b/examples/input_element/popup.rs index e004e8b..5c4f05f 100644 --- a/examples/input_element/popup.rs +++ b/examples/input_element/popup.rs @@ -28,8 +28,10 @@ impl ObjectImpl for InputPopup { self.width_request(300); self.height_request(300); - self.set_borders(1., 1., 1., 1.); + self.set_border_radius(6.); + // self.set_borders(1., 1., 1., 1.); self.set_border_color(Color::GREY_LIGHT); + self.set_background(Color::GREY_LIGHT); self.set_box_shadow(BoxShadow::new( 6., Color::BLACK, diff --git a/examples/list_view/list_view_holder.rs b/examples/list_view/list_view_holder.rs index ad618fd..6cbaa11 100644 --- a/examples/list_view/list_view_holder.rs +++ b/examples/list_view/list_view_holder.rs @@ -42,6 +42,7 @@ impl ObjectImpl for ListViewHolder { self.list_view_1.set_hexpand(true); self.list_view_1.set_vexpand(true); self.list_view_1.set_hscale(0.3); + self.list_view_1.set_line_height(40); self.list_view_2.set_hexpand(true); self.list_view_2.set_vexpand(true); @@ -125,7 +126,7 @@ impl ObjectImpl for ListViewHolder { } list.add_group(group); - for i in 10..1000000 { + for i in 10..100 { list.add_node(&Node { name: format!("Node_{}", i), }); diff --git a/examples/list_view/main.rs b/examples/list_view/main.rs index 2ed8287..e5c96eb 100644 --- a/examples/list_view/main.rs +++ b/examples/list_view/main.rs @@ -4,10 +4,11 @@ use list_view_holder::ListViewHolder; use tmui::{ application::Application, application_window::ApplicationWindow, + icons::svg_dom::SvgDom, prelude::*, views::{ cell::{ - cell_render::TextCellRender, + cell_render::{SvgCellRender, TextCellRender}, Cell, }, list_view::list_view_object::ListViewObject, @@ -39,14 +40,29 @@ struct Node { impl ListViewObject for Node { #[inline] fn cells(&self) -> Vec { + let dom = SvgDom::from_file("examples/resources/sword_rose.svg"); vec![ Cell::string() .value(self.name.clone()) - .cell_render(TextCellRender::builder().color(Color::BLACK).build()) + .cell_render( + TextCellRender::builder() + .color(Color::BLACK) + .halign(Align::Center) + .valign(Align::Center) + .build(), + ) .build(), Cell::string() .value(self.name.clone()) - .cell_render(TextCellRender::builder().color(Color::BLACK).build()) + .cell_render( + TextCellRender::builder() + .color(Color::BLACK) + .valign(Align::Center) + .build(), + ) + .build(), + Cell::svg() + .cell_render(SvgCellRender::builder().dom(Some(dom)).build()) .build(), ] } @@ -55,4 +71,4 @@ impl ListViewObject for Node { fn node_render(&self) -> NodeRender { NodeRender::builder().build() } -} \ No newline at end of file +} diff --git a/tmui/src/application_window.rs b/tmui/src/application_window.rs index f3b8200..a6c753c 100644 --- a/tmui/src/application_window.rs +++ b/tmui/src/application_window.rs @@ -95,6 +95,7 @@ pub struct ApplicationWindow { root_ancestors: Vec, win_widgets: Vec, removed: Vec, + radius_widgets: Vec, #[cfg(not(win_dialog))] input_dialog: Option>, @@ -776,14 +777,15 @@ impl ApplicationWindow { self.mouse_over_widget = None; } self.mouse_enter_widgets - .retain_mut(|r| nonnull_ref!(r).id() != id); - self.run_afters.retain_mut(|r| nonnull_ref!(r).id() != id); + .retain(|r| nonnull_ref!(r).id() != id); + self.run_afters.retain(|r| nonnull_ref!(r).id() != id); for map in self.watch_map.values_mut() { map.remove(&id); } self.overlaids.remove(&id); - self.root_ancestors.retain_mut(|r| *r != id); - self.win_widgets.retain_mut(|r| nonnull_ref!(r).id() != id); + self.root_ancestors.retain(|r| *r != id); + self.win_widgets.retain(|r| nonnull_ref!(r).id() != id); + self.radius_widgets.retain(|r| *r != id); nonnull_ref!(self.board).remove_element(id); AnimationMgr::with(|mgr| mgr.borrow_mut().remove_snapshot(id)); @@ -808,6 +810,21 @@ impl ApplicationWindow { &mut self.overlaids } + #[inline] + pub(crate) fn get_radius_widgets(&self) -> &[ObjectId] { + &self.radius_widgets + } + + #[inline] + pub(crate) fn remove_radius_widget(&mut self, id: ObjectId) { + self.radius_widgets.retain(|w| *w != id); + } + + #[inline] + pub(crate) fn add_radius_widget(&mut self, id: ObjectId) { + self.radius_widgets.push(id); + } + #[inline] pub(crate) fn set_modal_widget(&mut self, id: Option) { // Trigger the mouse leave method for all entered widgets diff --git a/tmui/src/graphics/painter.rs b/tmui/src/graphics/painter.rs index 8d3ffa2..fec8ed0 100644 --- a/tmui/src/graphics/painter.rs +++ b/tmui/src/graphics/painter.rs @@ -108,6 +108,11 @@ impl<'a> Painter<'a> { rect.offset(self.x_offset, self.y_offset) } + #[inline] + pub fn font(&self) -> Option<&Font> { + self.font.as_ref() + } + #[inline] pub fn set_transform(&mut self, transform: Matrix, combined: bool) { if combined { @@ -217,6 +222,12 @@ impl<'a> Painter<'a> { #[inline] pub fn translate(&self, dx: f32, dy: f32) { + self.canvas + .translate((dx + self.x_offset as f32, dy + self.y_offset as f32)); + } + + #[inline] + pub fn translate_global(&self, dx: f32, dy: f32) { self.canvas.translate((dx, dy)); } diff --git a/tmui/src/icons/svg_icon.rs b/tmui/src/icons/svg_icon.rs index 62403b0..804ac00 100644 --- a/tmui/src/icons/svg_icon.rs +++ b/tmui/src/icons/svg_icon.rs @@ -32,7 +32,7 @@ impl WidgetImpl for SvgIcon { let origin = FPoint::new(x1 + (w1 - w2) / 2., y1 + (h1 - h2) / 2.); painter.save(); - painter.translate(origin.x(), origin.y()); + painter.translate_global(origin.x(), origin.y()); painter.draw_dom(dom); painter.restore(); } diff --git a/tmui/src/icons/svg_toggle_icon.rs b/tmui/src/icons/svg_toggle_icon.rs index 9360706..35d17a9 100644 --- a/tmui/src/icons/svg_toggle_icon.rs +++ b/tmui/src/icons/svg_toggle_icon.rs @@ -36,7 +36,7 @@ impl WidgetImpl for SvgToggleIcon { fn paint(&mut self, painter: &mut Painter) { if let Some(dom) = self.doms.get(self.index) { painter.save(); - painter.translate(self.origin.x(), self.origin.y()); + painter.translate_global(self.origin.x(), self.origin.y()); painter.draw_dom(dom); painter.restore(); } diff --git a/tmui/src/input/number.rs b/tmui/src/input/number.rs index a0a2030..164f1a1 100644 --- a/tmui/src/input/number.rs +++ b/tmui/src/input/number.rs @@ -426,7 +426,7 @@ impl Number { let y = inner1.y() + (inner1.height() - ARROW_SIZE) / 2.; if let Some(ref dom) = self.arrow_up { painter.save(); - painter.translate(x, y); + painter.translate_global(x, y); painter.draw_dom(dom); painter.restore(); } @@ -435,7 +435,7 @@ impl Number { let y = inner2.y() + (inner1.height() - ARROW_SIZE) / 2.; if let Some(ref dom) = self.arrow_down { painter.save(); - painter.translate(x, y); + painter.translate_global(x, y); painter.draw_dom(dom); painter.restore(); } diff --git a/tmui/src/input/select/mod.rs b/tmui/src/input/select/mod.rs index ea11cf7..6b168b9 100644 --- a/tmui/src/input/select/mod.rs +++ b/tmui/src/input/select/mod.rs @@ -281,7 +281,7 @@ impl Select { if let Some(ref dom) = self.dom { painter.save(); let pos = self.arrow_pos(); - painter.translate(pos.x(), pos.y()); + painter.translate_global(pos.x(), pos.y()); painter.draw_dom(dom); painter.restore(); } diff --git a/tmui/src/views/cell/cell_render.rs b/tmui/src/views/cell/cell_render.rs index 8e3693c..39ef43c 100644 --- a/tmui/src/views/cell/cell_render.rs +++ b/tmui/src/views/cell/cell_render.rs @@ -7,7 +7,13 @@ use tlib::{ figure::{Color, FPoint, FRect}, global::{shown_value_32, shown_value_64}, namespace::BorderStyle, - skia_safe::ClipOp, + prelude::Align, + skia_safe::{ + textlayout::{ + FontCollection, ParagraphBuilder, ParagraphStyle, TextStyle, TypefaceFontProvider, + }, + ClipOp, + }, Type, Value, }; @@ -45,6 +51,14 @@ pub trait CellRender: Debug + 'static + Send + Sync { fn set_height(&mut self, height: u32); + fn halign(&self) -> Align; + + fn set_halign(&mut self, halign: Align); + + fn valign(&self) -> Align; + + fn set_valign(&mut self, valign: Align); + fn ty(&self) -> CellRenderType; } @@ -58,6 +72,8 @@ macro_rules! cell_render_struct { background: Option, width: Option, height: Option, + halign: Align, + valign: Align, ty: CellRenderType, $($field: $ty),* } @@ -70,6 +86,8 @@ macro_rules! cell_render_struct { background: Option, width: Option, height: Option, + halign: Align, + valign: Align, $($field: $ty),* } impl $cell { @@ -115,6 +133,18 @@ macro_rules! cell_render_struct { self } + #[inline] + pub fn halign(mut self, halign: Align) -> Self { + self.halign = halign; + self + } + + #[inline] + pub fn valign(mut self, valign: Align) -> Self { + self.valign = valign; + self + } + $( #[inline] pub fn $field(mut self, $field: $ty) -> Self { @@ -132,6 +162,8 @@ macro_rules! cell_render_struct { background: self.background, width: self.width, height: self.height, + halign: self.halign, + valign: self.valign, ty: CellRenderType::$render_type, $( $field: self.$field @@ -204,6 +236,26 @@ macro_rules! impl_cell_render_common { self.height = Some(height) } + #[inline] + fn halign(&self) -> Align { + self.halign + } + + #[inline] + fn set_halign(&mut self, halign: Align) { + self.halign = halign + } + + #[inline] + fn valign(&self) -> Align { + self.valign + } + + #[inline] + fn set_valign(&mut self, valign: Align) { + self.valign = valign + } + #[inline] fn ty(&self) -> CellRenderType { self.ty @@ -247,10 +299,42 @@ impl CellRender for TextCellRender { Type::F64 => shown_value_64(val.get::()), _ => "Unkonwn value.".to_string(), }; - let origin = geometry.top_left(); + let mut draw_point = geometry.top_left(); + let (paragraph_width, paragraph_height) = + self.calc_paragraph_size(painter, &text, geometry); + match self.halign() { + Align::Start => {} + Align::Center => { + let offset = (geometry.width() - paragraph_width) / 2.; + if offset > 0. { + draw_point.set_x(draw_point.x() + offset); + } + } + Align::End => { + let offset = geometry.width() - paragraph_width; + if offset > 0. { + draw_point.set_x(draw_point.x() + offset); + } + } + }; + match self.valign() { + Align::Start => {} + Align::Center => { + let offset = (geometry.height() - paragraph_height) / 2.; + if offset > 0. { + draw_point.set_y(draw_point.y() + offset); + } + } + Align::End => { + let offset = geometry.height() - paragraph_height; + if offset > 0. { + draw_point.set_y(draw_point.y() + offset); + } + } + }; painter.draw_paragraph( &text, - origin, + draw_point, self.letter_spacing, geometry.width(), Some(1), @@ -283,6 +367,49 @@ impl TextCellRender { pub fn letter_spacing(&self) -> f32 { self.letter_spacing } + + fn calc_paragraph_size(&self, painter: &mut Painter, text: &str, rect: FRect) -> (f32, f32) { + let font = painter.font().unwrap(); + + let mut typeface_provider = TypefaceFontProvider::new(); + let mut families = vec![]; + for tf in font.typefaces() { + let typeface = tf.to_skia_typeface(font); + + if let Some(typeface) = typeface { + families.push(tf.family()); + let family = typeface.family_name(); + typeface_provider.register_typeface(typeface, Some(family.as_str())); + } + } + + let mut font_collection = FontCollection::new(); + font_collection.set_asset_font_manager(Some(typeface_provider.clone().into())); + + // define text style + let mut style = ParagraphStyle::new(); + let mut text_style = TextStyle::new(); + text_style.set_font_size(font.size()); + text_style.set_font_families(&families); + text_style.set_letter_spacing(self.letter_spacing); + style.set_text_style(&text_style); + style.set_max_lines(Some(1)); + style.set_ellipsis("\u{2026}"); + + // layout the paragraph + let mut paragraph_builder = ParagraphBuilder::new(&style, font_collection); + paragraph_builder.add_text(text); + let mut paragraph = paragraph_builder.build(); + + let width = rect.width(); + let layout = if width == 0. { f32::MAX } else { width }; + paragraph.layout(layout); + + ( + paragraph.max_intrinsic_width().ceil(), + paragraph.height().ceil(), + ) + } } impl CellRender for ImageCellRender { @@ -299,6 +426,7 @@ impl CellRender for SvgCellRender { let (w2, h2) = (view_size.width() as f32, view_size.height() as f32); let origin = FPoint::new(x1 + (w1 - w2) / 2., y1 + (h1 - h2) / 2.); painter.save(); + painter.clip_rect(rect, ClipOp::Intersect); painter.translate(origin.x(), origin.y()); painter.draw_dom(dom); painter.restore(); diff --git a/tmui/src/views/list_view/list_node.rs b/tmui/src/views/list_view/list_node.rs index 2193cf9..2bc0865 100644 --- a/tmui/src/views/list_view/list_node.rs +++ b/tmui/src/views/list_view/list_node.rs @@ -4,10 +4,13 @@ use super::{ list_view_object::ListViewObject, ListView, Painter, }; -use crate::{application::is_ui_thread, views::{ - cell::{cell_index::CellIndex, cell_render::CellRender, Cell}, - node::{node_render::NodeRender, RenderCtx, Status}, -}}; +use crate::{ + application::is_ui_thread, + views::{ + cell::{cell_index::CellIndex, cell_render::CellRender, Cell}, + node::{node_render::NodeRender, RenderCtx, Status}, + }, +}; use log::warn; use tlib::{ global::AsAny, @@ -84,11 +87,17 @@ impl ListNode { .unwrap() } - pub fn get_value(&self, cell_idx: impl CellIndex) -> Option { + pub fn get_value( + &self, + cell_idx: impl CellIndex, + ) -> Option { self.cells .get(cell_idx.index()) .or_else(|| { - warn!("Undefined cell of tree view node, cell index: {:?}", cell_idx); + warn!( + "Undefined cell of tree view node, cell index: {:?}", + cell_idx + ); None }) .and_then(|cell| { @@ -122,7 +131,10 @@ impl ListNode { self.notify_update(); } } else { - warn!("Undefined cell of tree view node, cell index: {:?}", cell_idx); + warn!( + "Undefined cell of tree view node, cell index: {:?}", + cell_idx + ); } } @@ -199,8 +211,7 @@ impl ListItem for ListNode { fn render(&self, painter: &mut Painter, render_ctx: RenderCtx) { let geometry = render_ctx.geometry; - self.node_render - .render(painter, render_ctx, self.status); + self.node_render.render(painter, render_ctx, self.status); let gapping = geometry.width() / self.render_cell_size() as f32; let mut offset = geometry.x(); diff --git a/tmui/src/views/list_view/list_view_image.rs b/tmui/src/views/list_view/list_view_image.rs index 2e61e7d..36d71ba 100644 --- a/tmui/src/views/list_view/list_view_image.rs +++ b/tmui/src/views/list_view/list_view_image.rs @@ -31,6 +31,7 @@ pub(crate) struct ListViewImage { indent_length: i32, #[derivative(Default(value = "1"))] pub(crate) line_height: i32, + pub(crate) custom_line_height: bool, pub(crate) line_spacing: i32, #[derivative(Default(value = "MouseEffect::all()"))] pub(crate) mouse_effect: MouseEffect, @@ -190,6 +191,9 @@ impl ListViewImage { #[inline] fn on_font_changed(&mut self) { + if self.custom_line_height { + return; + } let (_, h) = self.font().calc_font_dimension(); self.line_height = h as i32; diff --git a/tmui/src/views/list_view/mod.rs b/tmui/src/views/list_view/mod.rs index a3fea12..cb25959 100644 --- a/tmui/src/views/list_view/mod.rs +++ b/tmui/src/views/list_view/mod.rs @@ -207,7 +207,8 @@ impl ListView { #[inline] pub fn set_line_height(&mut self, line_height: i32) { - self.get_image_mut().line_height = line_height + self.get_image_mut().line_height = line_height; + self.get_image_mut().custom_line_height = true; } #[inline] diff --git a/tmui/src/widget/mod.rs b/tmui/src/widget/mod.rs index f373c7f..1f320f3 100644 --- a/tmui/src/widget/mod.rs +++ b/tmui/src/widget/mod.rs @@ -512,6 +512,8 @@ impl ElementImpl for let _track = Tracker::start(format!("single_render_{}", self.name())); + let window = ApplicationWindow::window(); + let name = &self.name(); let mut painter = Painter::new(name, cr.canvas(), self); @@ -541,22 +543,28 @@ impl ElementImpl for painter.fill_rect_global(rect, Color::TRANSPARENT); painter.restore(); } else { - self.window().clip_window(&mut painter); + window.clip_window(&mut painter); } if self.id() != self.window_id() { self.clip_child_region(&mut painter); } if let Some(parent) = self.get_parent_ref() { - if parent.border_ref().should_draw_radius() { - let radius = parent.border_ref().border_radius; - painter.clip_round_rect_global(parent.rect(), radius, ClipOp::Intersect); - } else { - painter.clip_rect_global(parent.contents_rect(None), ClipOp::Intersect); + painter.clip_rect_global(parent.contents_rect(None), ClipOp::Intersect); + } + for &id in window.get_radius_widgets() { + if self.descendant_of(id) { + if let Some(w) = window.find_id(id) { + painter.clip_round_rect_global( + w.rect(), + w.border_ref().border_radius, + ClipOp::Intersect, + ); + } } } - let window_resized = self.window().is_resize_redraw(); + let window_resized = window.is_resize_redraw(); if (self.whole_styles_render() || self.is_resize_redraw() || window_resized) && !self.is_animation_progressing() { @@ -597,7 +605,7 @@ impl ElementImpl for // Draw the background color of the Widget. if self.is_render_difference() && self.first_rendered() - && !self.window().minimized() + && !window.minimized() && !cliped { painter.save(); diff --git a/tmui/src/widget/widget_ext.rs b/tmui/src/widget/widget_ext.rs index f0787b2..a4e9ab1 100644 --- a/tmui/src/widget/widget_ext.rs +++ b/tmui/src/widget/widget_ext.rs @@ -1460,6 +1460,15 @@ impl WidgetExt for T { self.set_whole_styles_render(true); self.update_render_styles(); + + let window = ApplicationWindow::window(); + if self.id() != window.id() { + if radius == 0. { + window.remove_radius_widget(self.id()); + } else { + window.add_radius_widget(self.id()); + } + } } #[inline] @@ -1469,6 +1478,15 @@ impl WidgetExt for T { self.set_whole_styles_render(true); self.update_render_styles(); + + let window = ApplicationWindow::window(); + if self.id() != window.id() { + if self.border_ref().should_draw_radius() { + window.add_radius_widget(self.id()); + } else { + window.remove_radius_widget(self.id()); + } + } } #[inline]