use std::hash::{Hash, Hasher}; use cosmic::{ iced::{alignment, Point}, iced_core::{ gradient, layout::{Layout, Limits, Node}, mouse::Cursor, renderer::{self, Renderer as IcedRenderer}, text::{LineHeight, Paragraph, Renderer as TextRenderer, Shaping}, widget::{tree, Tree, Widget}, Background, Color, Degrees, Gradient, Length, Rectangle, Size, Text, }, }; /// Text in a stack tab with an overflow gradient. pub fn tab_text(text: String) -> TabText { TabText::new(text) } struct LocalState { text_hash: u64, paragraph: ::Paragraph, overflowed: bool, } /// Text in a stack tab with an overflow gradient. pub struct TabText { text: String, font: cosmic::font::Font, font_size: f32, height: Length, width: Length, } impl TabText { pub fn new(text: String) -> Self { TabText { width: Length::Shrink, height: Length::Shrink, font: cosmic::font::DEFAULT, font_size: 14.0, text, } } pub fn font(mut self, font: cosmic::font::Font) -> Self { self.font = font; self } pub fn font_size(mut self, font_size: f32) -> Self { self.font_size = font_size; self } pub fn width(mut self, width: impl Into) -> Self { let width = width.into(); self.width = width; self } pub fn height(mut self, height: impl Into) -> Self { let height = height.into(); self.height = height; self } fn create_hash(&self) -> u64 { let mut hasher = std::collections::hash_map::DefaultHasher::new(); self.text.hash(&mut hasher); hasher.finish() } fn create_paragraph(&self) -> ::Paragraph { ::Paragraph::with_text(Text { content: &self.text, size: cosmic::iced_core::Pixels(self.font_size), bounds: Size::INFINITY, font: self.font, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, shaping: Shaping::Advanced, line_height: LineHeight::default(), }) } } impl Widget for TabText { fn tag(&self) -> tree::Tag { tree::Tag::of::() } fn state(&self) -> tree::State { tree::State::new(LocalState { text_hash: self.create_hash(), paragraph: self.create_paragraph(), overflowed: false, }) } fn width(&self) -> Length { self.width } fn height(&self) -> Length { self.height } fn layout(&self, tree: &mut Tree, _renderer: &cosmic::Renderer, limits: &Limits) -> Node { let limits = limits.width(self.width).height(self.height); let state = tree.state.downcast_mut::(); let text_bounds = state.paragraph.min_bounds(); state.overflowed = limits.max().width < text_bounds.width; let actual_size = limits.resolve(text_bounds); Node::new(actual_size) } fn diff(&mut self, tree: &mut Tree) { // If the text changes, update the paragraph. let state = tree.state.downcast_mut::(); let text_hash = self.create_hash(); if state.text_hash != text_hash { state.text_hash = text_hash; state.paragraph = self.create_paragraph(); } } fn draw( &self, tree: &Tree, renderer: &mut cosmic::Renderer, theme: &cosmic::Theme, style: &renderer::Style, layout: Layout<'_>, _cursor: Cursor, _viewport: &Rectangle, ) { let bounds = layout.bounds(); let state = tree.state.downcast_ref::(); renderer.with_layer(bounds, |renderer| { renderer.fill_paragraph( &state.paragraph, Point::new(bounds.x, bounds.y + bounds.height / 2.0), style.text_color, bounds, ); }); if state.overflowed { let background = super::tab::primary_container_color(theme.cosmic()); let transparent = Color { a: 0.0, ..background }; renderer.fill_quad( renderer::Quad { bounds: Rectangle { x: (bounds.x + bounds.width - 24.).max(bounds.x), width: 24.0_f32.min(bounds.width), ..bounds }, border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, Background::Gradient(Gradient::Linear( gradient::Linear::new(Degrees(90.)) .add_stop(0.0, transparent) .add_stop(1.0, background), )), ); } } } impl From for cosmic::Element<'_, Message> { fn from(value: TabText) -> Self { Self::new(value) } }