microui_redux/widgets/
display.rs1use crate::text_layout::build_text_lines;
55use crate::*;
56
57fn baseline_aligned_top(rect: Recti, line_height: i32, baseline: i32) -> i32 {
58 if rect.height >= line_height {
59 return rect.y + (rect.height - line_height) / 2;
60 }
61
62 let baseline_center = rect.y + rect.height / 2;
63 let min_top = rect.y + rect.height - line_height;
64 let max_top = rect.y;
65 (baseline_center - baseline).clamp(min_top, max_top)
66}
67
68fn text_lines<'a>(text: &'a str, wrap: TextWrap, max_width: i32, font: FontId, atlas: &AtlasHandle) -> Vec<crate::text_layout::TextLine> {
69 let mut lines = build_text_lines(text, wrap, max_width, font, atlas);
70 if text.ends_with('\n') {
71 if let Some(last) = lines.last() {
72 if last.start == text.len() && last.end == text.len() {
73 lines.pop();
74 }
75 }
76 }
77 lines
78}
79
80#[derive(Clone)]
81pub struct TextBlock {
83 pub text: String,
85 pub wrap: TextWrap,
87 pub font: FontChoice,
89 pub opt: WidgetOption,
91 pub bopt: WidgetBehaviourOption,
93}
94
95impl TextBlock {
96 pub fn new(text: impl Into<String>) -> Self {
98 Self::with_wrap(text, TextWrap::None)
99 }
100
101 pub fn with_wrap(text: impl Into<String>, wrap: TextWrap) -> Self {
103 Self {
104 text: text.into(),
105 wrap,
106 font: FontChoice::default(),
107 opt: WidgetOption::NO_INTERACT | WidgetOption::NO_FRAME,
108 bopt: WidgetBehaviourOption::NONE,
109 }
110 }
111
112 fn preferred_size_widget(&self, style: &Style, atlas: &AtlasHandle, avail: Dimensioni) -> Dimensioni {
113 if self.text.is_empty() {
114 return Dimensioni::new(0, 0);
115 }
116
117 let font = style.resolve_font_choice(self.font);
118 let line_height = atlas.get_font_height(font) as i32;
119 let max_width = if self.wrap == TextWrap::Word && avail.width > 0 {
120 avail.width.max(1)
121 } else {
122 i32::MAX / 4
123 };
124 let lines = text_lines(self.text.as_str(), self.wrap, max_width, font, atlas);
125 let width = lines.iter().map(|line| line.width).max().unwrap_or(0).max(0);
126 let height = line_height.saturating_mul((lines.len() as i32).max(1)).max(0);
127 Dimensioni::new(width, height)
128 }
129
130 fn handle_widget(&mut self, ctx: &mut WidgetCtx<'_>, _control: &ControlState) -> ResourceState {
131 if self.text.is_empty() {
132 return ResourceState::NONE;
133 }
134
135 let bounds = ctx.rect();
136 let font = ctx.style().resolve_font_choice(self.font);
137 let color = ctx.style().colors[ControlColor::Text as usize];
138 let line_height = ctx.atlas().get_font_height(font) as i32;
139 let baseline = ctx.atlas().get_font_baseline(font);
140 let max_width = if self.wrap == TextWrap::Word { bounds.width.max(1) } else { i32::MAX / 4 };
141 let lines = text_lines(self.text.as_str(), self.wrap, max_width, font, ctx.atlas());
142
143 ctx.push_clip_rect(bounds);
144 for (idx, line) in lines.iter().enumerate() {
145 let line_rect = rect(bounds.x, bounds.y + idx as i32 * line_height, bounds.width, line_height);
146 let line_top = baseline_aligned_top(line_rect, line_height, baseline);
147 let slice = &self.text[line.start..line.end];
148 if !slice.is_empty() {
149 ctx.draw_text(font, slice, vec2(line_rect.x, line_top), color);
150 }
151 }
152 ctx.pop_clip_rect();
153
154 ResourceState::NONE
155 }
156}
157
158implement_widget!(TextBlock, handle_widget, preferred_size_widget);
159
160#[derive(Clone)]
161pub struct ColorSwatch {
163 pub fill: Color,
165 pub label: String,
167 pub font: FontChoice,
169 pub opt: WidgetOption,
171 pub bopt: WidgetBehaviourOption,
173}
174
175impl ColorSwatch {
176 pub fn new(fill: Color) -> Self {
178 Self {
179 fill,
180 label: String::new(),
181 font: FontChoice::default(),
182 opt: WidgetOption::NO_INTERACT | WidgetOption::ALIGN_CENTER,
183 bopt: WidgetBehaviourOption::NONE,
184 }
185 }
186
187 fn preferred_size_widget(&self, style: &Style, atlas: &AtlasHandle, _avail: Dimensioni) -> Dimensioni {
188 let padding = style.padding.max(0);
189 let font = style.resolve_font_choice(self.font);
190 let label_width = if self.label.is_empty() {
191 0
192 } else {
193 atlas.get_text_size(font, self.label.as_str()).width.max(0)
194 };
195 let height = (atlas.get_font_height(font) as i32 + padding * 2).max(24);
196 Dimensioni::new((label_width + padding * 2).max(24), height)
197 }
198
199 fn handle_widget(&mut self, ctx: &mut WidgetCtx<'_>, _control: &ControlState) -> ResourceState {
200 let rect = ctx.rect();
201 ctx.draw_rect(rect, self.fill);
202 let border = ctx.style().colors[ControlColor::Border as usize];
203 ctx.draw_box(rect, border);
204 if !self.label.is_empty() {
205 let font = ctx.style().resolve_font_choice(self.font);
206 ctx.draw_control_text_with_font(font, self.label.as_str(), rect, ControlColor::Text, self.opt);
207 }
208 ResourceState::NONE
209 }
210}
211
212implement_widget!(ColorSwatch, handle_widget, preferred_size_widget);