xref: /StarryEngine/starry_toolkit/src/widgets/label.rs (revision 1bee64b64bc410ee78964a11a40a0fff69945480)
1 use std::{
2     any::Any,
3     cell::{Cell, RefCell},
4     cmp::max,
5     sync::{Arc, Weak},
6 };
7 
8 use starry_client::base::{color::Color, renderer::Renderer};
9 
10 use crate::{
11     base::{panel::Panel, rect::Rect, vector2::Vector2},
12     traits::text::Text,
13     util::{align_rect, get_local_rect},
14 };
15 
16 use super::{PivotType, Widget};
17 
18 #[derive(PartialEq, Copy, Clone)]
19 pub enum LabelOverflowType {
20     /// 不适配 溢出部分不显示
21     None,
22     /// 根据字数调整大小
23     ShinkToFit,
24     /// 省略多余内容
25     Omit,
26     // TODO 支持"调整字体大小以适配"的选项
27 }
28 
29 pub struct Label {
30     self_ref: RefCell<Weak<Label>>,
31     rect: Cell<Rect>,
32     pivot: Cell<PivotType>,
33     pivot_offset: Cell<Vector2>,
34     children: RefCell<Vec<Arc<dyn Widget>>>,
35     parent: RefCell<Option<Arc<dyn Widget>>>,
36     panel: RefCell<Option<Arc<Panel>>>,
37     /// 实际上的文本
38     real_text: RefCell<String>,
39     /// 用于显示的文本
40     show_text: RefCell<String>,
41     text_color: Cell<Color>,
42     adapt_type: Cell<LabelOverflowType>,
43     /// 渲染文本时的矩形区域
44     text_rect: Cell<Rect>,
45     /// 文本在矩形框内的对齐方式
46     text_pivot: Cell<PivotType>,
47 }
48 
49 // TODO 暂时只支持渲染一行字体
50 impl Label {
51     pub fn new() -> Arc<Self> {
52         let label = Arc::new(Label {
53             rect: Cell::new(Rect::default()),
54             pivot: Cell::new(PivotType::TopLeft),
55             pivot_offset: Cell::new(Vector2::new(0, 0)),
56             children: RefCell::new(vec![]),
57             parent: RefCell::new(None),
58             panel: RefCell::new(None),
59             real_text: RefCell::new(String::new()),
60             show_text: RefCell::new(String::new()),
61             text_color: Cell::new(Color::rgb(0, 0, 0)), // 默认黑色字体
62             adapt_type: Cell::new(LabelOverflowType::None),
63             text_rect: Cell::new(Rect::default()),
64             text_pivot: Cell::new(PivotType::Center),
65             self_ref: RefCell::new(Weak::new()),
66         });
67 
68         (*label.self_ref.borrow_mut()) = Arc::downgrade(&label);
69 
70         return label;
71     }
72 
73     /// 处理文本溢出的情况
74     /// 在文本内容改变或大小改变时调用
75     fn handle_overflow(&self) {
76         let text = self.real_text.borrow();
77 
78         match self.adapt_type.get() {
79             LabelOverflowType::None => {}
80             LabelOverflowType::ShinkToFit => {
81                 self.resize(text.len() as u32 * 8 as u32, 16);
82             }
83             LabelOverflowType::Omit => {
84                 let rect = self.rect.get();
85 
86                 if text.len() as u32 * 8 > rect.width {
87                     let max_count = max(0, (rect.width as i32 - 3 * 8) / 8);
88                     let mut omit_str = self.real_text.borrow().clone();
89                     let _ = omit_str.split_off(max_count as usize);
90                     omit_str.push_str("..."); // 溢出字符用省略号取代
91                     (*self.show_text.borrow_mut()) = omit_str;
92                 }
93             }
94         }
95 
96         self.text_rect.set(Rect::new(
97             0,
98             0,
99             self.show_text.borrow().len() as u32 * 8,
100             16,
101         ));
102     }
103 
104     pub fn set_adapt_type(&self, adapt_type: LabelOverflowType) {
105         self.adapt_type.set(adapt_type);
106     }
107 }
108 
109 impl Widget for Label {
110     fn self_ref(&self) -> Arc<dyn Widget> {
111         self.self_ref.borrow().upgrade().unwrap() as Arc<dyn Widget>
112     }
113 
114     fn as_any_ref(&self) -> &dyn Any {
115         self
116     }
117 
118     fn name(&self) -> &str {
119         "Label"
120     }
121 
122     fn rect(&self) -> &Cell<Rect> {
123         &self.rect
124     }
125 
126     fn pivot(&self) -> &Cell<PivotType> {
127         &self.pivot
128     }
129 
130     fn pivot_offset(&self) -> &Cell<Vector2> {
131         &self.pivot_offset
132     }
133 
134     fn parent(&self) -> &RefCell<Option<Arc<dyn Widget>>> {
135         &self.parent
136     }
137 
138     fn children(&self) -> &RefCell<Vec<Arc<dyn Widget>>> {
139         &self.children
140     }
141 
142     fn panel(&self) -> &RefCell<Option<Arc<crate::base::panel::Panel>>> {
143         &self.panel
144     }
145 
146     fn draw(&self, renderer: &mut dyn Renderer, _focused: bool) {
147         let origin_rect = self.text_rect.get();
148         let mut current_rect = self.text_rect.get(); // 当前字符渲染矩形
149         let origin_x = origin_rect.x;
150         let text = self.show_text.borrow().clone();
151 
152         // 矩形高度不满足
153         if origin_rect.height < 16 {
154             return;
155         }
156 
157         for char in text.chars() {
158             if char == '\n' {
159                 // 换行 退格到起始位置
160                 current_rect.x = origin_x;
161                 current_rect.y += 16;
162             } else {
163                 // 避免超出矩形范围
164                 if current_rect.x + 8 <= origin_rect.x + origin_rect.width as i32
165                     && current_rect.y + 16 <= origin_rect.y + origin_rect.height as i32
166                 {
167                     if self.panel().borrow().is_some() {
168                         let local_rect = get_local_rect(
169                             current_rect,
170                             self.panel().borrow().clone().unwrap().rect(),
171                         );
172                         // TODO 应用主题(Theme)颜色
173                         renderer.char(local_rect.x, local_rect.y, char, self.text_color.get());
174                     } else {
175                         println!("[Error] Label do not belong to any panel!");
176                     }
177                 }
178                 current_rect.x += 8;
179             }
180         }
181     }
182 
183     fn set_pivot_type(&self, pivot_type: PivotType) {
184         self.set_pivot_type_base(pivot_type);
185 
186         self.text_rect.set(align_rect(
187             self.text_rect.get(),
188             self.rect.get(),
189             self.text_pivot.get(),
190             Vector2::new(0, 0),
191         ));
192     }
193 
194     fn set_pivot_offset(&self, pivot_offset: Vector2) {
195         self.set_pivot_offset_base(pivot_offset);
196 
197         self.text_rect.set(align_rect(
198             self.text_rect.get(),
199             self.rect.get(),
200             self.text_pivot.get(),
201             Vector2::new(0, 0),
202         ));
203     }
204 
205     fn resize(&self, width: u32, height: u32) {
206         self.resize_base(width, height);
207 
208         self.handle_overflow();
209         self.text_rect.set(align_rect(
210             self.text_rect.get(),
211             self.rect.get(),
212             self.text_pivot.get(),
213             Vector2::new(0, 0),
214         ));
215     }
216 
217     fn arrange_self(&self) {
218         self.arrange_self_base();
219 
220         self.text_rect.set(align_rect(
221             self.text_rect.get(),
222             self.rect.get(),
223             self.text_pivot.get(),
224             Vector2::new(0, 0),
225         ));
226     }
227 }
228 
229 impl Text for Label {
230     fn set_text<S: Into<String>>(&self, text: S) -> &Self {
231         let text = text.into();
232         (*self.real_text.borrow_mut()) = text.clone();
233         (*self.show_text.borrow_mut()) = text;
234         self.handle_overflow();
235         align_rect(
236             self.text_rect.get(),
237             self.rect.get(),
238             self.text_pivot.get(),
239             Vector2::new(0, 0),
240         );
241         self
242     }
243 
244     fn set_text_color(&self, color: Color) -> &Self {
245         self.text_color.set(color);
246         self
247     }
248 }
249