azul_core/
ui_solver.rs

1use std::{fmt, collections::BTreeMap};
2use azul_css::{
3    LayoutRect, LayoutPoint, LayoutSize, PixelValue, StyleFontSize,
4    StyleTextColor, ColorU as StyleColorU, Overflow,
5    StyleTextAlignmentHorz, StyleTextAlignmentVert,
6};
7use crate::{
8    app_resources::{Words, ScaledWords, FontInstanceKey, WordPositions, LayoutedGlyphs},
9    id_tree::{NodeId, NodeDataContainer},
10    dom::{DomHash, ScrollTagId},
11    callbacks::PipelineId,
12};
13
14pub const DEFAULT_FONT_SIZE_PX: isize = 16;
15pub const DEFAULT_FONT_SIZE: StyleFontSize = StyleFontSize(PixelValue::const_px(DEFAULT_FONT_SIZE_PX));
16pub const DEFAULT_FONT_ID: &str = "serif";
17pub const DEFAULT_FONT_COLOR: StyleTextColor = StyleTextColor(StyleColorU { r: 0, b: 0, g: 0, a: 255 });
18pub const DEFAULT_LINE_HEIGHT: f32 = 1.0;
19pub const DEFAULT_WORD_SPACING: f32 = 1.0;
20pub const DEFAULT_LETTER_SPACING: f32 = 0.0;
21pub const DEFAULT_TAB_WIDTH: f32 = 4.0;
22
23#[derive(Debug, Clone, PartialEq, PartialOrd)]
24pub struct InlineTextLayout {
25    pub lines: Vec<InlineTextLine>,
26}
27
28#[derive(Debug, Clone, PartialEq, PartialOrd)]
29pub struct InlineTextLine {
30    pub bounds: LayoutRect,
31    /// At which word does this line start?
32    pub word_start: usize,
33    /// At which word does this line end
34    pub word_end: usize,
35}
36
37impl InlineTextLine {
38    pub const fn new(bounds: LayoutRect, word_start: usize, word_end: usize) -> Self {
39        Self { bounds, word_start, word_end }
40    }
41}
42
43impl InlineTextLayout {
44
45    pub fn get_leading(&self) -> f32 {
46        match self.lines.first() {
47            None => 0.0,
48            Some(s) => s.bounds.origin.x,
49        }
50    }
51
52    pub fn get_trailing(&self) -> f32 {
53        match self.lines.first() {
54            None => 0.0,
55            Some(s) => s.bounds.origin.x + s.bounds.size.width,
56        }
57    }
58
59    pub const fn new(lines: Vec<InlineTextLine>) -> Self {
60        Self { lines }
61    }
62
63    #[inline]
64    #[must_use]
65    pub fn get_bounds(&self) -> LayoutRect {
66        LayoutRect::union(self.lines.iter().map(|c| c.bounds)).unwrap_or(LayoutRect::zero())
67    }
68
69    #[must_use]
70    pub fn get_children_horizontal_diff_to_right_edge(&self, parent: &LayoutRect) -> Vec<f32> {
71        let parent_right_edge = parent.origin.x + parent.size.width;
72        let parent_left_edge = parent.origin.x;
73        self.lines.iter().map(|line| {
74            let child_right_edge = line.bounds.origin.x + line.bounds.size.width;
75            let child_left_edge = line.bounds.origin.x;
76            (child_left_edge - parent_left_edge) + (parent_right_edge - child_right_edge)
77        }).collect()
78    }
79
80    /// Align the lines horizontal to *their bounding box*
81    pub fn align_children_horizontal(&mut self, horizontal_alignment: StyleTextAlignmentHorz) {
82        let shift_multiplier = match calculate_horizontal_shift_multiplier(horizontal_alignment) {
83            None =>  return,
84            Some(s) => s,
85        };
86        let self_bounds = self.get_bounds();
87        let horz_diff = self.get_children_horizontal_diff_to_right_edge(&self_bounds);
88
89        for (line, shift) in self.lines.iter_mut().zip(horz_diff.into_iter()) {
90            line.bounds.origin.x += shift * shift_multiplier;
91        }
92    }
93
94    /// Align the lines vertical to *their parents container*
95    pub fn align_children_vertical_in_parent_bounds(&mut self, parent_size: &LayoutSize, vertical_alignment: StyleTextAlignmentVert) {
96
97        let shift_multiplier = match calculate_vertical_shift_multiplier(vertical_alignment) {
98            None =>  return,
99            Some(s) => s,
100        };
101
102        let self_bounds = self.get_bounds();
103        let child_bottom_edge = self_bounds.origin.y + self_bounds.size.height;
104        let child_top_edge = self_bounds.origin.y;
105        let shift = child_top_edge + (parent_size.height - child_bottom_edge);
106
107        for line in self.lines.iter_mut() {
108            line.bounds.origin.y += shift * shift_multiplier;
109        }
110    }
111}
112
113#[inline]
114pub fn calculate_horizontal_shift_multiplier(horizontal_alignment: StyleTextAlignmentHorz) -> Option<f32> {
115    use azul_css::StyleTextAlignmentHorz::*;
116    match horizontal_alignment {
117        Left => None,
118        Center => Some(0.5), // move the line by the half width
119        Right => Some(1.0), // move the line by the full width
120    }
121}
122
123#[inline]
124pub fn calculate_vertical_shift_multiplier(vertical_alignment: StyleTextAlignmentVert) -> Option<f32> {
125    use azul_css::StyleTextAlignmentVert::*;
126    match vertical_alignment {
127        Top => None,
128        Center => Some(0.5), // move the line by the half width
129        Bottom => Some(1.0), // move the line by the full width
130    }
131}
132
133#[derive(Clone, Copy, Eq, Hash, PartialEq, Ord, PartialOrd)]
134#[repr(C)]
135pub struct ExternalScrollId(pub u64, pub PipelineId);
136
137impl ::std::fmt::Display for ExternalScrollId {
138    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139        write!(f, "ExternalScrollId({:0x}, {})", self.0, self.1)
140    }
141}
142
143impl ::std::fmt::Debug for ExternalScrollId {
144    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145        write!(f, "{}", self)
146    }
147}
148
149#[derive(Default, Debug, Clone)]
150pub struct ScrolledNodes {
151    pub overflowing_nodes: BTreeMap<NodeId, OverflowingScrollNode>,
152    pub tags_to_node_ids: BTreeMap<ScrollTagId, NodeId>,
153}
154
155#[derive(Debug, Clone)]
156pub struct OverflowingScrollNode {
157    pub child_rect: LayoutRect,
158    pub parent_external_scroll_id: ExternalScrollId,
159    pub parent_dom_hash: DomHash,
160    pub scroll_tag_id: ScrollTagId,
161}
162
163#[derive(Debug, Default, Clone)]
164pub struct LayoutResult {
165    pub rects: NodeDataContainer<PositionedRectangle>,
166    pub word_cache: BTreeMap<NodeId, Words>,
167    pub scaled_words: BTreeMap<NodeId, (ScaledWords, FontInstanceKey)>,
168    pub positioned_word_cache: BTreeMap<NodeId, (WordPositions, FontInstanceKey)>,
169    pub layouted_glyph_cache: BTreeMap<NodeId, LayoutedGlyphs>,
170    pub node_depths: Vec<(usize, NodeId)>,
171}
172
173/// Layout options that can impact the flow of word positions
174#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
175pub struct TextLayoutOptions {
176    /// Font size (in pixels) that this text has been laid out with
177    pub font_size_px: PixelValue,
178    /// Multiplier for the line height, default to 1.0
179    pub line_height: Option<f32>,
180    /// Additional spacing between glyphs (in pixels)
181    pub letter_spacing: Option<PixelValue>,
182    /// Additional spacing between words (in pixels)
183    pub word_spacing: Option<PixelValue>,
184    /// How many spaces should a tab character emulate
185    /// (multiplying value, i.e. `4.0` = one tab = 4 spaces)?
186    pub tab_width: Option<f32>,
187    /// Maximum width of the text (in pixels) - if the text is set to `overflow:visible`, set this to None.
188    pub max_horizontal_width: Option<f32>,
189    /// How many pixels of leading does the first line have? Note that this added onto to the holes,
190    /// so for effects like `:first-letter`, use a hole instead of a leading.
191    pub leading: Option<f32>,
192    /// This is more important for inline text layout where items can punch "holes"
193    /// into the text flow, for example an image that floats to the right.
194    ///
195    /// TODO: Currently unused!
196    pub holes: Vec<LayoutRect>,
197}
198
199/// Same as `TextLayoutOptions`, but with the widths / heights of the `PixelValue`s
200/// resolved to regular f32s (because `letter_spacing`, `word_spacing`, etc. may be %-based value)
201#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
202pub struct ResolvedTextLayoutOptions {
203    /// Font size (in pixels) that this text has been laid out with
204    pub font_size_px: f32,
205    /// Multiplier for the line height, default to 1.0
206    pub line_height: Option<f32>,
207    /// Additional spacing between glyphs (in pixels)
208    pub letter_spacing: Option<f32>,
209    /// Additional spacing between words (in pixels)
210    pub word_spacing: Option<f32>,
211    /// How many spaces should a tab character emulate
212    /// (multiplying value, i.e. `4.0` = one tab = 4 spaces)?
213    pub tab_width: Option<f32>,
214    /// Maximum width of the text (in pixels) - if the text is set to `overflow:visible`, set this to None.
215    pub max_horizontal_width: Option<f32>,
216    /// How many pixels of leading does the first line have? Note that this added onto to the holes,
217    /// so for effects like `:first-letter`, use a hole instead of a leading.
218    pub leading: Option<f32>,
219    /// This is more important for inline text layout where items can punch "holes"
220    /// into the text flow, for example an image that floats to the right.
221    ///
222    /// TODO: Currently unused!
223    pub holes: Vec<LayoutRect>,
224}
225
226#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
227pub struct ResolvedOffsets {
228    pub top: f32,
229    pub left: f32,
230    pub right: f32,
231    pub bottom: f32,
232}
233
234impl ResolvedOffsets {
235    pub const fn zero() -> Self { Self { top: 0.0, left: 0.0, right: 0.0, bottom: 0.0 } }
236    pub fn total_vertical(&self) -> f32 { self.top + self.bottom }
237    pub fn total_horizontal(&self) -> f32 { self.left + self.right }
238}
239
240#[derive(Debug, Clone, PartialEq, PartialOrd)]
241pub struct PositionedRectangle {
242    /// Outer bounds of the rectangle
243    pub size: LayoutSize,
244    /// How the rectangle should be positioned
245    pub position: PositionInfo,
246    /// Padding of the rectangle
247    pub padding: ResolvedOffsets,
248    /// Margin of the rectangle
249    pub margin: ResolvedOffsets,
250    /// Border widths of the rectangle
251    pub border_widths: ResolvedOffsets,
252    /// If this is an inline rectangle, resolve the %-based font sizes
253    /// and store them here.
254    pub resolved_text_layout_options: Option<(ResolvedTextLayoutOptions, InlineTextLayout, LayoutRect)>,
255    /// Determines if the rect should be clipped or not (TODO: x / y as separate fields!)
256    pub overflow: Overflow,
257}
258
259#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
260pub enum PositionInfo {
261    Static { x_offset: f32, y_offset: f32 },
262    Fixed { x_offset: f32, y_offset: f32 },
263    Absolute { x_offset: f32, y_offset: f32 },
264    Relative { x_offset: f32, y_offset: f32 },
265}
266
267impl PositionInfo {
268    pub fn is_positioned(&self) -> bool {
269        match self {
270            PositionInfo::Static { .. } => false,
271            PositionInfo::Fixed { .. } => true,
272            PositionInfo::Absolute { .. } => true,
273            PositionInfo::Relative { .. } => true,
274        }
275    }
276}
277impl PositionedRectangle {
278
279    pub fn get_static_bounds(&self) -> Option<LayoutRect> {
280        match self.position {
281            PositionInfo::Static { x_offset, y_offset }     => Some(LayoutRect::new(
282                LayoutPoint::new(x_offset, y_offset), self.size
283            )),
284            PositionInfo::Fixed { .. }      => None,
285            PositionInfo::Absolute { .. }   => None, // TODO?
286            PositionInfo::Relative { x_offset, y_offset }   => Some(LayoutRect::new(
287                LayoutPoint::new(x_offset, y_offset), self.size
288            )), // TODO?
289        }
290    }
291
292    pub fn to_layouted_rectangle(&self) -> LayoutedRectangle {
293        LayoutedRectangle {
294            size: self.size,
295            position: self.position,
296            padding: self.padding,
297            margin: self.margin,
298            border_widths: self.border_widths,
299            overflow: self.overflow,
300        }
301    }
302
303    // Returns the rect where the content should be placed (for example the text itself)
304    pub fn get_content_size(&self) -> LayoutSize {
305        self.size
306    }
307
308    // Returns the rect that includes bounds, expanded by the padding + the border widths
309    pub fn get_background_bounds(&self) -> (LayoutSize, PositionInfo) {
310
311        use crate::ui_solver::PositionInfo::*;
312
313        let b_size = LayoutSize {
314            width: self.size.width + self.padding.total_horizontal() + self.border_widths.total_horizontal(),
315            height: self.size.height + self.padding.total_vertical() + self.border_widths.total_vertical(),
316        };
317
318        let x_offset_add = 0.0 - self.padding.left - self.border_widths.left;
319        let y_offset_add = 0.0 - self.padding.top - self.border_widths.top;
320
321        let b_position = match self.position {
322            Static { x_offset, y_offset } => Static { x_offset: x_offset + x_offset_add, y_offset: y_offset + y_offset_add },
323            Fixed { x_offset, y_offset } => Fixed { x_offset: x_offset + x_offset_add, y_offset: y_offset + y_offset_add },
324            Relative { x_offset, y_offset } => Relative { x_offset: x_offset + x_offset_add, y_offset: y_offset + y_offset_add },
325            Absolute { x_offset, y_offset } => Absolute { x_offset: x_offset + x_offset_add, y_offset: y_offset + y_offset_add },
326        };
327
328        (b_size, b_position)
329    }
330
331    pub fn get_margin_box_width(&self) -> f32 {
332        self.size.width +
333        self.padding.total_horizontal() +
334        self.border_widths.total_horizontal() +
335        self.margin.total_horizontal()
336    }
337
338    pub fn get_margin_box_height(&self) -> f32 {
339        self.size.height +
340        self.padding.total_vertical() +
341        self.border_widths.total_vertical() +
342        self.margin.total_vertical()
343    }
344
345    pub fn get_left_leading(&self) -> f32 {
346        self.margin.left +
347        self.padding.left +
348        self.border_widths.left
349    }
350
351    pub fn get_top_leading(&self) -> f32 {
352        self.margin.top +
353        self.padding.top +
354        self.border_widths.top
355    }
356}
357
358/// Same as `PositionedRectangle`, but without the `text_layout_options`,
359/// so that the struct implements `Copy`.
360#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
361pub struct LayoutedRectangle {
362    /// Outer bounds of the rectangle
363    pub size: LayoutSize,
364    /// How the rectangle should be positioned
365    pub position: PositionInfo,
366    /// Padding of the rectangle
367    pub padding: ResolvedOffsets,
368    /// Margin of the rectangle
369    pub margin: ResolvedOffsets,
370    /// Border widths of the rectangle
371    pub border_widths: ResolvedOffsets,
372    /// Determines if the rect should be clipped or not (TODO: x / y as separate fields!)
373    pub overflow: Overflow,
374}