1use crate::color::sRGB;
5use crate::component::{EventRouter, StateMachine};
6use crate::graphics::point_to_pixel;
7use crate::layout::{self, Layout, leaf};
8use crate::{SourceID, graphics};
9use cosmic_text::{LineIter, Metrics};
10use derive_where::derive_where;
11use std::cell::RefCell;
12use std::convert::Infallible;
13use std::rc::Rc;
14use std::sync::Arc;
15
16#[derive(Clone)]
17pub struct TextState {
18 buffer: Rc<RefCell<cosmic_text::Buffer>>,
19 text: String,
20 align: Option<cosmic_text::Align>,
21}
22
23impl EventRouter for TextState {
24 type Input = Infallible;
25 type Output = Infallible;
26}
27
28impl PartialEq for TextState {
29 fn eq(&self, other: &Self) -> bool {
30 Rc::ptr_eq(&self.buffer, &other.buffer)
31 && self.text == other.text
32 && self.align == other.align
33 }
34}
35
36#[derive_where(Clone)]
37pub struct Text<T> {
38 pub id: Arc<SourceID>,
39 pub props: Rc<T>,
40 pub font_size: f32,
41 pub line_height: f32,
42 pub text: String,
43 pub font: cosmic_text::FamilyOwned,
44 pub color: sRGB,
45 pub weight: cosmic_text::Weight,
46 pub style: cosmic_text::Style,
47 pub wrap: cosmic_text::Wrap,
48 pub align: Option<cosmic_text::Align>, }
52
53impl<T: leaf::Padded + 'static> Text<T> {
54 pub fn new(
55 id: Arc<SourceID>,
56 props: T,
57 font_size: f32,
58 line_height: f32,
59 text: String,
60 font: cosmic_text::FamilyOwned,
61 color: sRGB,
62 weight: cosmic_text::Weight,
63 style: cosmic_text::Style,
64 wrap: cosmic_text::Wrap,
65 align: Option<cosmic_text::Align>,
66 ) -> Self {
67 Self {
68 id,
69 props: props.into(),
70 font_size,
71 line_height,
72 text,
73 font,
74 color,
75 weight,
76 style,
77 wrap,
78 align,
79 }
80 }
81}
82
83impl<T: leaf::Padded + 'static> crate::StateMachineChild for Text<T> {
84 fn id(&self) -> Arc<SourceID> {
85 self.id.clone()
86 }
87
88 fn init(
89 &self,
90 _: &std::sync::Weak<graphics::Driver>,
91 ) -> Result<Box<dyn super::StateMachineWrapper>, crate::Error> {
92 let statemachine: StateMachine<TextState, 0> = StateMachine {
93 state: TextState {
94 buffer: Rc::new(RefCell::new(cosmic_text::Buffer::new_empty(Metrics::new(
95 point_to_pixel(self.font_size, 1.0),
96 point_to_pixel(self.line_height, 1.0),
97 )))),
98 text: String::new(),
99 align: None,
100 },
101 input_mask: 0,
102 output: [],
103 changed: true,
104 };
105 Ok(Box::new(statemachine))
106 }
107}
108
109impl<T: Default + leaf::Padded + 'static> Default for Text<T> {
110 fn default() -> Self {
111 Self {
112 id: Default::default(),
113 props: Default::default(),
114 font_size: Default::default(),
115 line_height: Default::default(),
116 text: Default::default(),
117 font: cosmic_text::FamilyOwned::SansSerif,
118 color: sRGB::new(1.0, 1.0, 1.0, 1.0),
119 weight: Default::default(),
120 style: Default::default(),
121 wrap: cosmic_text::Wrap::None,
122 align: None,
123 }
124 }
125}
126
127fn buffer_eq(s: &str, b: &cosmic_text::Buffer) -> bool {
128 let mut ranges = LineIter::new(s);
129 let mut lines = b.lines.iter();
130 loop {
131 match (lines.next(), ranges.next()) {
132 (Some(line), Some((r, _))) => {
133 if &s[r] != line.text() {
134 return false;
135 }
136 }
137 (None, None) => return true,
138 _ => return false,
139 }
140 }
141}
142
143impl<T: leaf::Padded + 'static> super::Component for Text<T>
144where
145 for<'a> &'a T: Into<&'a (dyn leaf::Padded + 'static)>,
146{
147 type Props = T;
148
149 fn layout(
150 &self,
151 manager: &mut crate::StateManager,
152 driver: &graphics::Driver,
153 window: &Arc<SourceID>,
154 ) -> Box<dyn Layout<T>> {
155 let dpi = manager
156 .get::<super::window::WindowStateMachine>(window)
157 .map(|x| x.state.dpi)
158 .unwrap_or(crate::BASE_DPI);
159 let mut font_system = driver.font_system.write();
160
161 let metrics = cosmic_text::Metrics::new(
162 point_to_pixel(self.font_size, dpi.width),
163 point_to_pixel(self.line_height, dpi.height),
164 );
165
166 let textstate = manager
167 .get_mut::<StateMachine<TextState, 0>>(&self.id)
168 .unwrap();
169 let textstate = &mut textstate.state;
170 textstate
171 .buffer
172 .borrow_mut()
173 .set_metrics(&mut font_system, metrics);
174 textstate
175 .buffer
176 .borrow_mut()
177 .set_wrap(&mut font_system, self.wrap);
178
179 if self.align != textstate.align || !buffer_eq(&self.text, &textstate.buffer.borrow()) {
180 textstate.buffer.borrow_mut().set_text(
181 &mut font_system,
182 &self.text,
183 &cosmic_text::Attrs::new()
184 .family(self.font.as_family())
185 .color(self.color.into())
186 .weight(self.weight)
187 .style(self.style),
188 cosmic_text::Shaping::Advanced,
189 self.align,
190 );
191
192 textstate.text = self.text.clone();
193 textstate.align = self.align;
194 }
195
196 let render = Rc::new(crate::render::text::Instance {
197 text_buffer: textstate.buffer.clone(),
198 padding: self.props.padding().as_perimeter(dpi).into(),
199 });
200
201 Box::new(layout::text::Node::<T> {
202 props: self.props.clone(),
203 id: Arc::downgrade(&self.id),
204 buffer: textstate.buffer.clone(),
205 renderable: render.clone(),
206 realign: self.align.is_some_and(|x| x != cosmic_text::Align::Left),
207 })
208 }
209}