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