1use linear_map::LinearMap;
9use std::any::Any;
10use std::f32;
11use std::rc::Rc;
12
13use crate::anim::AnimState;
14use kas::cast::traits::*;
15use kas::dir::Directional;
16use kas::geom::{Rect, Size, Vec2};
17use kas::layout::{AlignPair, AxisInfo, FrameRules, Margins, SizeRules, Stretch};
18use kas::text::{fonts::FontId, TextApi, TextApiExt};
19use kas::theme::{Feature, FrameStyle, MarginStyle, MarkStyle, TextClass, ThemeSize};
20
21kas::impl_scope! {
22 #[derive(Clone, Debug)]
29 #[impl_default]
30 pub struct Parameters {
31 pub m_inner: f32 = 1.25,
38 pub m_tiny: f32 = 2.4,
42 pub m_small: f32 = 4.35,
46 pub m_large: f32 = 7.4,
50 pub m_text: (f32, f32) = (3.3, 0.8),
52 pub frame: f32 = 2.4,
54 pub popup_frame: f32 = 0.0,
56 pub menu_frame: f32 = 2.4,
58 pub button_frame: f32 = 2.4,
60 pub button_inner: f32 = 0.0,
62 pub check_box: f32 = 18.0,
64 pub mark: f32 = 10.0,
66 pub handle_len: f32 = 16.0,
68 pub scroll_bar_size: Vec2 = Vec2(24.0, 8.0),
70 pub slider_size: Vec2 = Vec2(24.0, 12.0),
72 pub progress_bar: Vec2 = Vec2(24.0, 8.0),
74 pub shadow_size: Vec2 = Vec2::ZERO,
76 pub shadow_rel_offset: Vec2 = Vec2::ZERO,
78 }
79}
80
81#[derive(Clone, Debug)]
83pub struct Dimensions {
84 pub scale: f32,
86 pub dpem: f32,
87 pub mark_line: f32,
88 pub min_line_length: i32,
89 pub m_inner: u16,
90 pub m_tiny: u16,
91 pub m_small: u16,
92 pub m_large: u16,
93 pub m_text: (u16, u16),
94 pub frame: i32,
95 pub popup_frame: i32,
96 pub menu_frame: i32,
97 pub button_frame: i32,
98 pub button_inner: u16,
99 pub check_box: i32,
100 pub mark: i32,
101 pub handle_len: i32,
102 pub scroll_bar: Size,
103 pub slider: Size,
104 pub progress_bar: Size,
105 pub shadow_a: Vec2,
106 pub shadow_b: Vec2,
107}
108
109impl Dimensions {
110 pub fn new(params: &Parameters, pt_size: f32, scale: f32) -> Self {
111 let dpp = scale * (96.0 / 72.0);
112 let dpem = dpp * pt_size;
113
114 let text_m0 = (params.m_text.0 * scale).cast_nearest();
115 let text_m1 = (params.m_text.1 * scale).cast_nearest();
116
117 let shadow_size = params.shadow_size * scale;
118 let shadow_offset = shadow_size * params.shadow_rel_offset;
119
120 Dimensions {
121 scale,
122 dpem,
123 mark_line: (1.6 * scale).round().max(1.0),
124 min_line_length: (8.0 * dpem).cast_nearest(),
125 m_inner: (params.m_inner * scale).cast_nearest(),
126 m_tiny: (params.m_tiny * scale).cast_nearest(),
127 m_small: (params.m_small * scale).cast_nearest(),
128 m_large: (params.m_large * scale).cast_nearest(),
129 m_text: (text_m0, text_m1),
130 frame: (params.frame * scale).cast_nearest(),
131 popup_frame: (params.popup_frame * scale).cast_nearest(),
132 menu_frame: (params.menu_frame * scale).cast_nearest(),
133 button_frame: (params.button_frame * scale).cast_nearest(),
134 button_inner: (params.button_inner * scale).cast_nearest(),
135 check_box: i32::conv_nearest(params.check_box * scale),
136 mark: i32::conv_nearest(params.mark * scale),
137 handle_len: i32::conv_nearest(params.handle_len * scale),
138 scroll_bar: Size::conv_nearest(params.scroll_bar_size * scale),
139 slider: Size::conv_nearest(params.slider_size * scale),
140 progress_bar: Size::conv_nearest(params.progress_bar * scale),
141 shadow_a: shadow_offset - shadow_size,
142 shadow_b: shadow_offset + shadow_size,
143 }
144 }
145}
146
147pub struct Window<D> {
149 pub dims: Dimensions,
150 pub fonts: Rc<LinearMap<TextClass, FontId>>,
151 pub anim: AnimState<D>,
152}
153
154impl<D> Window<D> {
155 pub fn new(
156 dims: &Parameters,
157 config: &crate::Config,
158 scale: f32,
159 fonts: Rc<LinearMap<TextClass, FontId>>,
160 ) -> Self {
161 Window {
162 dims: Dimensions::new(dims, config.font_size(), scale),
163 fonts,
164 anim: AnimState::new(config),
165 }
166 }
167
168 pub fn update(&mut self, dims: &Parameters, config: &crate::Config, scale: f32) {
169 self.dims = Dimensions::new(dims, config.font_size(), scale);
170 }
171}
172
173impl<D: 'static> crate::Window for Window<D> {
174 fn size(&self) -> &dyn ThemeSize {
175 self
176 }
177
178 fn as_any_mut(&mut self) -> &mut dyn Any {
179 self
180 }
181}
182
183impl<D: 'static> ThemeSize for Window<D> {
184 fn scale_factor(&self) -> f32 {
185 self.dims.scale
186 }
187
188 fn dpem(&self) -> f32 {
189 self.dims.dpem
190 }
191
192 fn min_scroll_size(&self, axis_is_vertical: bool) -> i32 {
193 if axis_is_vertical {
194 (self.dims.dpem * 3.0).cast_ceil()
195 } else {
196 self.dims.min_line_length
197 }
198 }
199
200 fn handle_len(&self) -> i32 {
201 self.dims.handle_len
202 }
203
204 fn scroll_bar_width(&self) -> i32 {
205 self.dims.scroll_bar.1
206 }
207
208 fn margins(&self, style: MarginStyle) -> Margins {
209 match style {
210 MarginStyle::None => Margins::ZERO,
211 MarginStyle::Inner => Margins::splat(self.dims.m_inner),
212 MarginStyle::Tiny => Margins::splat(self.dims.m_tiny),
213 MarginStyle::Small => Margins::splat(self.dims.m_small),
214 MarginStyle::Large => Margins::splat(self.dims.m_large),
215 MarginStyle::Text => Margins::hv_splat(self.dims.m_text),
216 MarginStyle::Px(px) => Margins::splat(u16::conv_nearest(px * self.dims.scale)),
217 MarginStyle::Em(em) => Margins::splat(u16::conv_nearest(em * self.dims.dpem)),
218 }
219 }
220
221 fn feature(&self, feature: Feature, axis_is_vertical: bool) -> SizeRules {
222 let dir_is_vertical;
223 let mut size;
224 let mut ideal_mul = 3;
225 let m;
226
227 match feature {
228 Feature::Separator => {
229 return SizeRules::fixed_splat(self.dims.frame, 0);
230 }
231 Feature::Mark(MarkStyle::Point(dir)) => {
232 let w = match dir.is_vertical() == axis_is_vertical {
233 true => self.dims.mark / 2 + i32::conv_ceil(self.dims.mark_line),
234 false => self.dims.mark + i32::conv_ceil(self.dims.mark_line),
235 };
236 return SizeRules::fixed_splat(w, self.dims.m_tiny);
237 }
238 Feature::CheckBox | Feature::RadioBox => {
239 return SizeRules::fixed_splat(self.dims.check_box, self.dims.m_small);
240 }
241 Feature::ScrollBar(dir) => {
242 dir_is_vertical = dir.is_vertical();
243 size = self.dims.scroll_bar;
244 m = 0;
245 }
246 Feature::Slider(dir) => {
247 dir_is_vertical = dir.is_vertical();
248 size = self.dims.slider;
249 ideal_mul = 5;
250 m = self.dims.m_large;
251 }
252 Feature::ProgressBar(dir) => {
253 dir_is_vertical = dir.is_vertical();
254 size = self.dims.progress_bar;
255 m = self.dims.m_large;
256 }
257 }
258
259 let mut stretch = Stretch::High;
260 if dir_is_vertical != axis_is_vertical {
261 size = size.transpose();
262 ideal_mul = 1;
263 stretch = Stretch::None;
264 }
265 SizeRules::new(size.0, ideal_mul * size.0, (m, m), stretch)
266 }
267
268 fn align_feature(&self, feature: Feature, rect: Rect, align: AlignPair) -> Rect {
269 let mut ideal_size = rect.size;
270 match feature {
271 Feature::Separator => (), Feature::Mark(_) => (), Feature::CheckBox | Feature::RadioBox => {
274 ideal_size = Size::splat(self.dims.check_box);
275 }
276 Feature::ScrollBar(dir) => {
277 ideal_size.set_component(dir.flipped(), self.dims.scroll_bar.1);
278 }
279 Feature::Slider(dir) => {
280 ideal_size.set_component(dir.flipped(), self.dims.slider.1);
281 }
282 Feature::ProgressBar(dir) => {
283 ideal_size.set_component(dir.flipped(), self.dims.progress_bar.1);
284 }
285 }
286 align.aligned_rect(ideal_size, rect)
287 }
288
289 fn frame(&self, style: FrameStyle, _is_vert: bool) -> FrameRules {
290 let outer = self.dims.m_large;
291 match style {
292 FrameStyle::Frame => FrameRules::new_sym(self.dims.frame, 0, outer),
293 FrameStyle::Popup => FrameRules::new_sym(self.dims.popup_frame, 0, 0),
294 FrameStyle::MenuEntry => FrameRules::new_sym(self.dims.menu_frame, 0, 0),
295 FrameStyle::NavFocus => FrameRules::new_sym(0, self.dims.m_inner, 0),
296 FrameStyle::Button => {
297 FrameRules::new_sym(self.dims.button_frame, self.dims.button_inner, outer)
298 }
299 FrameStyle::EditBox => FrameRules::new_sym(self.dims.frame, 0, outer),
300 }
301 }
302
303 fn line_height(&self, class: TextClass) -> i32 {
304 let font_id = self.fonts.get(&class).cloned().unwrap_or_default();
305 kas::text::fonts::fonts()
306 .get_first_face(font_id)
307 .expect("invalid font_id")
308 .height(self.dims.dpem)
309 .cast_ceil()
310 }
311
312 fn text_rules(&self, text: &mut dyn TextApi, class: TextClass, axis: AxisInfo) -> SizeRules {
313 let margin = match axis.is_horizontal() {
314 true => self.dims.m_text.0,
315 false => self.dims.m_text.1,
316 };
317 let margins = (margin, margin);
318
319 let mut env = text.env();
320
321 if let Some(font_id) = self.fonts.get(&class).cloned() {
323 env.font_id = font_id;
324 }
325 env.dpem = self.dims.dpem;
326 let wrap = class.multi_line();
329 env.wrap = wrap;
330 let align = axis.align_or_default();
331 if axis.is_horizontal() {
332 env.align.0 = align;
333 } else {
334 env.align.1 = align;
335 }
336 if let Some(size) = axis.size_other_if_fixed(true) {
337 env.bounds.0 = size.cast();
338 }
339
340 text.set_env(env);
341
342 if axis.is_horizontal() {
343 if wrap {
344 let min = self.dims.min_line_length;
345 let limit = 2 * min;
346 let bound: i32 = text
347 .measure_width(limit.cast())
348 .expect("invalid font_id")
349 .cast_ceil();
350
351 SizeRules::new(bound.min(min), bound.min(limit), margins, Stretch::Filler)
355 } else {
356 let bound: i32 = text
357 .measure_width(f32::INFINITY)
358 .expect("invalid font_id")
359 .cast_ceil();
360 SizeRules::new(bound, bound, margins, Stretch::Filler)
361 }
362 } else {
363 let bound: i32 = text.measure_height().expect("invalid font_id").cast_ceil();
364
365 let line_height = self.dims.dpem.cast_ceil();
366 let min = bound.max(line_height);
367 SizeRules::new(min, min, margins, Stretch::Filler)
368 }
369 }
370
371 fn text_set_size(
372 &self,
373 text: &mut dyn TextApi,
374 class: TextClass,
375 size: Size,
376 align: Option<AlignPair>,
377 ) {
378 let mut env = text.env();
379 if let Some(font_id) = self.fonts.get(&class).cloned() {
380 env.font_id = font_id;
381 }
382 env.dpem = self.dims.dpem;
383 env.wrap = class.multi_line();
384 if let Some(align) = align {
385 env.align = align.into();
386 }
387 env.bounds = size.cast();
388 text.update_env(env).expect("invalid font_id");
389 }
390}