///|
type DragGestureMessageHandler[Msg] = (DragGestureEvent) -> Msg
///|
type FrameDragGestureMessageHandler[Msg] = (DragGestureEvent, Rect) -> Msg
///|
type FileDropMessageHandler[Msg] = (Array[String], Point) -> Msg
///|
priv struct FrameModifierSpec {
width : Double?
height : Double?
min_width : Double?
min_height : Double?
max_width : Double?
max_height : Double?
}
///|
///
#warnings("-unused_field")
priv enum ViewModifier[Msg] {
KeyModifier(Key)
PaddingModifier(Insets)
FrameModifier(FrameModifierSpec)
BackgroundModifier(Brush)
ForegroundModifier(Color)
FontModifier(FontSpec)
CornerRadiusModifier(Double)
OpacityModifier(Double)
ShadowModifier(ShadowStyle)
BorderModifier(BorderStyle)
OffsetModifier(Double, Double)
ClipModifier
ScaleModifier(Double, Double)
DisabledModifier(Bool)
AccessibilityLabelModifier(String)
SemanticsRoleModifier(SemanticsRole)
SemanticsSelectedModifier(Bool)
SemanticsPressedModifier(Bool)
SemanticsCheckedModifier(Bool)
SemanticsExpandedModifier(Bool)
SemanticsInvalidModifier(Bool)
SemanticsRequiredModifier(Bool)
SemanticsDescriptionModifier(String)
SemanticsActionModifier(SemanticsAction)
FocusableModifier(Bool)
FocusTrapModifier(Bool)
OnTapMessageModifier(Msg)
OnLongPressMessageModifier(Msg)
OnDoubleTapMessageModifier(Msg)
OnDragMessageModifier(DragGestureMessageHandler[Msg])
OnDragWithFrameMessageModifier(FrameDragGestureMessageHandler[Msg])
OnFileDropMessageModifier(FileDropMessageHandler[Msg])
KeyboardShortcutModifier(KeyboardShortcut)
FlexibleModifier(Double)
LayoutPriorityModifier(Double)
AlignModifier(Alignment)
SpacingModifier(Double)
MainAxisAlignmentModifier(MainAxisAlignment)
CrossAxisAlignmentModifier(CrossAxisAlignment)
ThemeModifier(Theme)
EnvironmentModifier(Environment)
AspectRatioModifier(Double)
IntrinsicWidthModifier
IntrinsicHeightModifier
RepaintBoundaryModifier
}
///|
fn[Msg] View::apply_modifier(
self : View[Msg],
modifier : ViewModifier[Msg],
) -> View[Msg] {
modifier_view(self, modifier)
}
///|
fn[Msg] modifier_view(
child : View[Msg],
modifier : ViewModifier[Msg],
) -> View[Msg] {
View::node(
identity=() => modifier_identity(child, modifier),
revision=() => "modifier:\{modifier_revision(modifier)}:\{child.revision()}",
children=() => [child],
child_constraints=constraints => {
modifier_child_constraints(modifier, constraints)
},
child_environment=environment => {
modifier_child_environment(modifier, environment)
},
child_style=style => modifier_child_style(modifier, style),
layout=ctx => modifier_layout(modifier, ctx),
paint=ctx => modifier_paint(modifier, ctx),
event=ctx => modifier_event(modifier, ctx),
text_input_state=ctx => child.text_input_state(ctx),
text_command=(ctx, intent, paste_text) => {
child.text_command_result(ctx, intent, paste_text~)
},
semantics=() => modifier_semantics(modifier),
focusable=() => modifier_focusable(modifier),
flex_weight=() => modifier_flex_weight(modifier),
)
}
///|
fn[Msg] modifier_identity(
child : View[Msg],
modifier : ViewModifier[Msg],
) -> ViewIdentity {
match modifier {
KeyModifier(key) => {
let identity = child.identity()
{ kind: identity.kind, key: Some(key) }
}
_ => {
let identity = child.identity()
{
kind: "Modified<\{modifier_kind(modifier)}>/\{identity.kind}",
key: identity.key,
}
}
}
}
///|
fn[Msg] modifier_kind(modifier : ViewModifier[Msg]) -> String {
match modifier {
KeyModifier(_) => "Key"
PaddingModifier(_) => "Padding"
FrameModifier(_) => "Frame"
BackgroundModifier(_) => "Background"
ForegroundModifier(_) => "Foreground"
FontModifier(_) => "Font"
CornerRadiusModifier(_) => "CornerRadius"
OpacityModifier(_) => "Opacity"
ShadowModifier(_) => "Shadow"
BorderModifier(_) => "Border"
OffsetModifier(_, _) => "Offset"
ClipModifier => "Clip"
ScaleModifier(_, _) => "Scale"
DisabledModifier(_) => "Disabled"
AccessibilityLabelModifier(_) => "AccessibilityLabel"
SemanticsRoleModifier(_) => "SemanticsRole"
SemanticsSelectedModifier(_) => "SemanticsSelected"
SemanticsPressedModifier(_) => "SemanticsPressed"
SemanticsCheckedModifier(_) => "SemanticsChecked"
SemanticsExpandedModifier(_) => "SemanticsExpanded"
SemanticsInvalidModifier(_) => "SemanticsInvalid"
SemanticsRequiredModifier(_) => "SemanticsRequired"
SemanticsDescriptionModifier(_) => "SemanticsDescription"
SemanticsActionModifier(_) => "SemanticsAction"
FocusableModifier(_) => "Focusable"
FocusTrapModifier(_) => "FocusTrap"
OnTapMessageModifier(_) => "OnTap"
OnLongPressMessageModifier(_) => "OnLongPress"
OnDoubleTapMessageModifier(_) => "OnDoubleTap"
OnDragMessageModifier(_) => "OnDrag"
OnDragWithFrameMessageModifier(_) => "OnDragWithFrame"
OnFileDropMessageModifier(_) => "OnFileDrop"
KeyboardShortcutModifier(_) => "KeyboardShortcut"
FlexibleModifier(_) => "Flexible"
LayoutPriorityModifier(_) => "LayoutPriority"
AlignModifier(_) => "Align"
SpacingModifier(_) => "Spacing"
MainAxisAlignmentModifier(_) => "MainAxisAlignment"
CrossAxisAlignmentModifier(_) => "CrossAxisAlignment"
ThemeModifier(_) => "Theme"
EnvironmentModifier(_) => "Environment"
AspectRatioModifier(_) => "AspectRatio"
IntrinsicWidthModifier => "IntrinsicWidth"
IntrinsicHeightModifier => "IntrinsicHeight"
RepaintBoundaryModifier => "RepaintBoundary"
}
}
///|
fn[Msg] modifier_revision(modifier : ViewModifier[Msg]) -> String {
match modifier {
KeyModifier(key) => "key:\{key.value}"
PaddingModifier(insets) =>
"padding:\{insets.top}:\{insets.right}:\{insets.bottom}:\{insets.left}"
FrameModifier(data) =>
"frame:\{option_double_revision(data.width)}:\{option_double_revision(data.height)}:\{option_double_revision(data.min_width)}:\{option_double_revision(data.min_height)}:\{option_double_revision(data.max_width)}:\{option_double_revision(data.max_height)}"
BackgroundModifier(brush) => "background:\{brush_revision(brush)}"
ForegroundModifier(color) => "foreground:\{color_revision(color)}"
FontModifier(font) => "font:\{font.size}:\{font.weight}"
CornerRadiusModifier(radius) => "corner-radius:\{radius}"
OpacityModifier(opacity) => "opacity:\{opacity}"
ShadowModifier(style) =>
"shadow:\{color_revision(style.color)}:\{style.offset.x}:\{style.offset.y}:\{style.blur_radius}:\{style.spread}"
BorderModifier(style) =>
"border:\{brush_revision(style.brush)}:\{style.width}"
OffsetModifier(dx, dy) => "offset:\{dx}:\{dy}"
ClipModifier => "clip"
ScaleModifier(sx, sy) => "scale:\{sx}:\{sy}"
DisabledModifier(disabled) => "disabled:\{disabled}"
AccessibilityLabelModifier(label) => "accessibility-label:\{label}"
SemanticsRoleModifier(role) => "semantics-role:\{role_label(role)}"
SemanticsSelectedModifier(selected) => "semantics-selected:\{selected}"
SemanticsPressedModifier(pressed) => "semantics-pressed:\{pressed}"
SemanticsCheckedModifier(checked) => "semantics-checked:\{checked}"
SemanticsExpandedModifier(expanded) => "semantics-expanded:\{expanded}"
SemanticsInvalidModifier(invalid) => "semantics-invalid:\{invalid}"
SemanticsRequiredModifier(required) => "semantics-required:\{required}"
SemanticsDescriptionModifier(description) =>
"semantics-description:\{description}"
SemanticsActionModifier(action) =>
"semantics-action:\{action_label(action)}"
FocusableModifier(focusable) => "focusable:\{focusable}"
FocusTrapModifier(enabled) => "focus-trap:\{enabled}"
OnTapMessageModifier(_) => "on-tap"
OnLongPressMessageModifier(_) => "on-long-press"
OnDoubleTapMessageModifier(_) => "on-double-tap"
OnDragMessageModifier(_) => "on-drag"
OnDragWithFrameMessageModifier(_) => "on-drag-with-frame"
OnFileDropMessageModifier(_) => "on-file-drop"
KeyboardShortcutModifier(shortcut) =>
"keyboard-shortcut:\{shortcut.key}:\{shortcut.modifiers.meta}:\{shortcut.modifiers.control}:\{shortcut.modifiers.alt}:\{shortcut.modifiers.shift}"
FlexibleModifier(weight) => "flexible:\{weight}"
LayoutPriorityModifier(priority) => "layout-priority:\{priority}"
AlignModifier(alignment) => "align:\{alignment.x}:\{alignment.y}"
SpacingModifier(spacing) => "spacing:\{spacing}"
MainAxisAlignmentModifier(alignment) =>
"main-axis-alignment:\{main_axis_alignment_label(alignment)}"
CrossAxisAlignmentModifier(alignment) =>
"cross-axis-alignment:\{cross_axis_alignment_label(alignment)}"
ThemeModifier(theme) => theme.revision_fingerprint()
EnvironmentModifier(env) => env.revision_fingerprint()
AspectRatioModifier(ratio) => "aspect-ratio:\{ratio}"
IntrinsicWidthModifier => "intrinsic-width"
IntrinsicHeightModifier => "intrinsic-height"
RepaintBoundaryModifier => "repaint-boundary"
}
}
///|
fn[Msg] modifier_child_constraints(
modifier : ViewModifier[Msg],
constraints : Constraints,
) -> Constraints {
match modifier {
PaddingModifier(insets) => constraints.deflate(insets)
FrameModifier(data) =>
frame_modifier_constraints_for_measure(data, constraints)
_ => constraints.loosen()
}
}
///|
fn[Msg] modifier_child_environment(
modifier : ViewModifier[Msg],
environment : Environment,
) -> Environment {
match modifier {
ThemeModifier(theme) => environment.with_theme(theme)
EnvironmentModifier(value) => value
DisabledModifier(disabled) => environment.with_enabled(!disabled)
_ => environment
}
}
///|
fn[Msg] modifier_child_style(
modifier : ViewModifier[Msg],
style : ViewStyle,
) -> ViewStyle {
match modifier {
ForegroundModifier(color) => { ..style, foreground: Some(color) }
FontModifier(font) => { ..style, font: Some(font) }
CornerRadiusModifier(radius) => { ..style, corner_radius: Some(radius) }
BackgroundModifier(brush) => { ..style, background: Some(brush) }
_ => style
}
}
///|
fn[Msg] modifier_layout(
modifier : ViewModifier[Msg],
ctx : ViewLayoutContext,
) -> ViewLayoutResult {
let child_size = first_modifier_child_size(ctx.child_sizes)
let size = ctx.constraints.constrain(
match modifier {
PaddingModifier(insets) => child_size.inflate(insets)
FrameModifier(data) => resolve_frame_modifier_size(data, child_size)
AspectRatioModifier(ratio) => aspect_ratio_size(child_size, ratio)
// Tight-fit (flex-basis 0): measured size is zero so the parent flex
// distributes the full main-axis space by weight without leaking the
// child's greedy measurement into the cached intrinsic size.
FlexibleModifier(_) => Size::new(width=0.0, height=0.0)
_ => child_size
},
)
ViewLayoutResult::new(size~, child_frames=[
modifier_child_frame(modifier, size, child_size),
])
}
///|
fn[Msg] modifier_child_frame(
modifier : ViewModifier[Msg],
size : Size,
child_size : Size,
) -> Rect {
let frame = Rect::new(x=0.0, y=0.0, width=size.width, height=size.height)
match modifier {
PaddingModifier(insets) => frame.inset(insets)
FrameModifier(data) => frame_rect_for_modifier(data, frame)
AlignModifier(alignment) => aligned_rect(frame, child_size, alignment)
OffsetModifier(dx, dy) => frame.offset(dx~, dy~)
AspectRatioModifier(ratio) => aspect_ratio_rect(frame, ratio)
_ => frame
}
}
///|
fn[Msg] modifier_paint(
modifier : ViewModifier[Msg],
ctx : ViewPaintContext,
) -> ViewPaintPlan {
match modifier {
BackgroundModifier(brush) => {
let radius = match ctx.style.corner_radius {
Some(value) => value
None => 0.0
}
ViewPaintPlan::commands([
DrawCommand::FillRoundedRectBrush(
RoundedRect::new(rect=ctx.frame, radius~),
brush,
),
])
}
OpacityModifier(opacity) =>
{
commands: [DrawCommand::PushOpacity(opacity)],
child_layers: [],
post_commands: [DrawCommand::PopOpacity],
overlay_commands: [],
platform_views: [],
repaint_boundary: false,
animating: false,
}
ShadowModifier(style) =>
ViewPaintPlan::commands([
DrawCommand::DrawShadow(
ShadowSpec::new(
rect=RoundedRect::new(rect=ctx.frame, radius=0.0),
color=style.color,
offset=style.offset,
blur_radius=style.blur_radius,
spread=style.spread,
),
),
])
BorderModifier(style) =>
if style.width > 0.0 {
let radius = match ctx.style.corner_radius {
Some(value) => value
None => 0.0
}
ViewPaintPlan::commands([
DrawCommand::StrokeRoundedRectBrush(
RoundedRect::new(rect=ctx.frame, radius~),
style.brush,
style.width,
),
])
} else {
ViewPaintPlan::empty()
}
ClipModifier =>
{
commands: [DrawCommand::PushClip(ctx.frame)],
child_layers: [],
post_commands: [DrawCommand::PopClip],
overlay_commands: [],
platform_views: [],
repaint_boundary: false,
animating: false,
}
ScaleModifier(sx, sy) =>
{
commands: [DrawCommand::PushTransform(Transform2D::scale(sx~, sy~))],
child_layers: [],
post_commands: [DrawCommand::PopTransform],
overlay_commands: [],
platform_views: [],
repaint_boundary: false,
animating: false,
}
DisabledModifier(true) =>
{
commands: [DrawCommand::PushOpacity(0.42)],
child_layers: [],
post_commands: [DrawCommand::PopOpacity],
overlay_commands: [],
platform_views: [],
repaint_boundary: false,
animating: false,
}
RepaintBoundaryModifier =>
{ ..ViewPaintPlan::empty(), repaint_boundary: true }
_ => ViewPaintPlan::empty()
}
}
///|
fn[Msg] modifier_event(
modifier : ViewModifier[Msg],
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match modifier {
DisabledModifier(true) => ViewEventResult::ignored()
OnTapMessageModifier(message) => handle_tap_modifier(message, ctx)
OnLongPressMessageModifier(message) =>
handle_long_press_modifier(message, ctx)
OnDoubleTapMessageModifier(message) =>
handle_double_tap_modifier(message, ctx)
OnDragMessageModifier(handler) =>
handle_drag_modifier(ctx, handler=(event, _frame) => handler(event))
OnDragWithFrameMessageModifier(handler) =>
handle_drag_modifier(ctx, handler~)
OnFileDropMessageModifier(handler) =>
handle_file_drop_modifier(handler, ctx)
KeyboardShortcutModifier(shortcut) =>
handle_keyboard_shortcut(shortcut, ctx)
FocusableModifier(true) => handle_focusable_modifier(ctx)
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_tap_modifier(
message : Msg,
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match ctx.event {
Pointer(event) => {
let inside = ctx.frame.contains(event.position)
let current = view_state_to_button_state(ctx.state)
let tap = recognize_button_tap(current, inside, event.phase)
let next_state = button_state_to_view_state(ctx.state, tap.next_state)
let changed = next_state != ctx.state || tap.activated
{
changed,
activated: tap.activated,
focused: false,
captured: inside || current == ButtonState::Pressed,
state: Some(next_state),
text_control: None,
dirty: if changed {
ViewDirtyHint::ViewPaintDirty
} else {
ViewDirtyHint::ViewClean
},
messages: if tap.activated {
[message]
} else {
[]
},
}
}
Keyboard(event) =>
if event.pressed && (event.key == "Enter" || event.key == " ") {
ViewEventResult::message(message)
} else {
ViewEventResult::ignored()
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_long_press_modifier(
message : Msg,
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match ctx.event {
Pointer(event) => {
let result = LongPressGestureState::new().update(
event,
inside=ctx.frame.contains(event.position),
)
if result.recognized {
ViewEventResult::message(message)
} else {
{
..ViewEventResult::ignored(),
changed: result.cancelled,
captured: result.cancelled,
dirty: if result.cancelled {
ViewDirtyHint::ViewPaintDirty
} else {
ViewDirtyHint::ViewClean
},
}
}
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_double_tap_modifier(
message : Msg,
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match ctx.event {
Pointer(event) => {
let result = DoubleTapGestureState::new().update(
event,
inside=ctx.frame.contains(event.position),
)
if result.recognized {
ViewEventResult::message(message)
} else {
ViewEventResult::ignored()
}
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_drag_modifier(
ctx : ViewEventContext,
handler~ : (DragGestureEvent, Rect) -> Msg,
) -> ViewEventResult[Msg] {
match ctx.event {
Pointer(event) => {
let result = DragGestureState::new().update(
event,
inside=ctx.frame.contains(event.position),
)
match result.event {
Some(drag_event) => {
let frame = match ctx.child_frames {
[child_frame, ..] => child_frame
[] => ctx.frame
}
{
changed: true,
activated: true,
focused: false,
captured: true,
state: None,
text_control: None,
dirty: ViewDirtyHint::ViewPaintDirty,
messages: [handler(drag_event, frame)],
}
}
None => ViewEventResult::ignored()
}
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_file_drop_modifier(
handler : (Array[String], Point) -> Msg,
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match ctx.event {
DragDrop(event) =>
match (event.phase, event.position) {
(Dropped, Some(position)) =>
if ctx.frame.contains(position) && event.paths.length() > 0 {
{
changed: true,
activated: true,
focused: false,
captured: true,
state: None,
text_control: None,
dirty: ViewDirtyHint::ViewPaintDirty,
messages: [handler(event.paths, position)],
}
} else {
ViewEventResult::ignored()
}
_ => ViewEventResult::ignored()
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_keyboard_shortcut(
shortcut : KeyboardShortcut,
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match ctx.event {
Keyboard(event) =>
if shortcut.matches(event) {
{ ..ViewEventResult::ignored(), captured: true }
} else {
ViewEventResult::ignored()
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] handle_focusable_modifier(
ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
match ctx.event {
Keyboard(event) =>
if event.pressed && (event.key == "Enter" || event.key == " ") {
{ ..ViewEventResult::ignored(), activated: true, captured: true }
} else {
ViewEventResult::ignored()
}
Pointer(event) =>
if event.phase == PointerPhase::Down && ctx.frame.contains(event.position) {
{ ..ViewEventResult::ignored(), focused: true, captured: true }
} else {
ViewEventResult::ignored()
}
_ => ViewEventResult::ignored()
}
}
///|
fn[Msg] modifier_semantics(modifier : ViewModifier[Msg]) -> ViewSemanticsInfo {
match modifier {
AccessibilityLabelModifier(label) =>
ViewSemanticsInfo::new(label~, value=label)
SemanticsRoleModifier(role) => ViewSemanticsInfo::new(role~)
DisabledModifier(disabled) => ViewSemanticsInfo::new(disabled~)
FocusableModifier(focusable) =>
ViewSemanticsInfo::new(actions=if focusable { [Focus] } else { [] })
OnTapMessageModifier(_)
| OnLongPressMessageModifier(_)
| OnDoubleTapMessageModifier(_)
| OnDragMessageModifier(_)
| OnDragWithFrameMessageModifier(_)
| KeyboardShortcutModifier(_) =>
ViewSemanticsInfo::new(role=SemanticsRole::Button, actions=[
Focus,
Activate,
])
SemanticsSelectedModifier(selected) =>
ViewSemanticsInfo::new(selected~, actions=[SemanticsAction::Select])
SemanticsPressedModifier(pressed) => ViewSemanticsInfo::new(pressed~)
SemanticsCheckedModifier(checked) => ViewSemanticsInfo::new(checked~)
SemanticsExpandedModifier(expanded) =>
ViewSemanticsInfo::new(
expanded~,
actions=if expanded {
[SemanticsAction::Collapse]
} else {
[SemanticsAction::Expand]
},
)
SemanticsInvalidModifier(invalid) => ViewSemanticsInfo::new(invalid~)
SemanticsRequiredModifier(required) => ViewSemanticsInfo::new(required~)
SemanticsDescriptionModifier(description) =>
ViewSemanticsInfo::new(description~)
SemanticsActionModifier(action) => ViewSemanticsInfo::new(actions=[action])
_ => ViewSemanticsInfo::new()
}
}
///|
fn[Msg] modifier_focusable(modifier : ViewModifier[Msg]) -> Bool {
match modifier {
FocusableModifier(value) => value
OnTapMessageModifier(_)
| OnLongPressMessageModifier(_)
| OnDoubleTapMessageModifier(_)
| KeyboardShortcutModifier(_) => true
_ => false
}
}
///|
/// `FlexibleModifier` (applied via `View::flexible` / `View::expanded`)
/// declares a flex growth weight so the parent `row`/`column` distributes
/// leftover main-axis space to this node. All other modifiers pass the
/// child's own weight through unchanged.
fn[Msg] modifier_flex_weight(modifier : ViewModifier[Msg]) -> Double {
match modifier {
FlexibleModifier(weight) => weight
_ => 0.0
}
}
///|
fn first_modifier_child_size(sizes : Array[Size]) -> Size {
match sizes {
[size, ..] => size
[] => Size::new(width=0.0, height=0.0)
}
}
///|
fn frame_modifier_constraints_for_measure(
data : FrameModifierSpec,
constraints : Constraints,
) -> Constraints {
match (data.width, data.height) {
(Some(width), Some(height)) => constraints.tighten(width~, height~).loosen()
(Some(width), None) => constraints.tighten(width~).loosen()
(None, Some(height)) => constraints.tighten(height~).loosen()
(None, None) => constraints.loosen()
}
}
///|
fn resolve_frame_modifier_size(data : FrameModifierSpec, child : Size) -> Size {
let width = data.width.unwrap_or(child.width)
let height = data.height.unwrap_or(child.height)
let min_width = data.min_width.unwrap_or(0.0)
let min_height = data.min_height.unwrap_or(0.0)
let max_width = data.max_width.unwrap_or(width)
let max_height = data.max_height.unwrap_or(height)
Size::new(width~, height~).clamp({
min: Size::new(width=min_width, height=min_height),
max: Size::new(
width=max_double(min_width, max_width),
height=max_double(min_height, max_height),
),
})
}
///|
fn frame_rect_for_modifier(data : FrameModifierSpec, bounds : Rect) -> Rect {
let size = resolve_frame_modifier_size(data, bounds.size)
Rect::new(
x=bounds.origin.x,
y=bounds.origin.y,
width=size.width,
height=size.height,
)
}
///|
fn aligned_rect(
bounds : Rect,
child_size : Size,
alignment : Alignment,
) -> Rect {
let width = min_double(bounds.size.width, child_size.width)
let height = min_double(bounds.size.height, child_size.height)
let dx = max_double(0.0, bounds.size.width - width) *
(alignment.x + 1.0) /
2.0
let dy = max_double(0.0, bounds.size.height - height) *
(alignment.y + 1.0) /
2.0
Rect::new(x=bounds.origin.x + dx, y=bounds.origin.y + dy, width~, height~)
}
///|
fn aspect_ratio_size(size : Size, ratio : Double) -> Size {
if size.width > 0.0 {
Size::new(width=size.width, height=size.width / ratio)
} else if size.height > 0.0 {
Size::new(width=size.height * ratio, height=size.height)
} else {
size
}
}
///|
fn aspect_ratio_rect(frame : Rect, ratio : Double) -> Rect {
let size = aspect_ratio_size(frame.size, ratio).clamp(
Constraints::tight(frame.size),
)
Rect::new(
x=frame.origin.x,
y=frame.origin.y,
width=size.width,
height=size.height,
)
}
///|
fn view_state_to_button_state(state : ViewStateContext) -> ButtonState {
if state.pressed {
ButtonState::Pressed
} else if state.hovered {
ButtonState::Hovered
} else {
ButtonState::Normal
}
}
///|
fn button_state_to_view_state(
state : ViewStateContext,
button_state : ButtonState,
) -> ViewStateContext {
match button_state {
ButtonState::Normal => { ..state, hovered: false, pressed: false }
ButtonState::Hovered => { ..state, hovered: true, pressed: false }
ButtonState::Pressed => { ..state, hovered: true, pressed: true }
}
}
///|
fn option_double_revision(value : Double?) -> String {
match value {
Some(value) => "\{value}"
None => "none"
}
}
///|
fn color_revision(color : Color) -> String {
"\{color.r},\{color.g},\{color.b},\{color.a}"
}
///|
fn brush_revision(value : Brush) -> String {
match value {
Brush::Solid(color) => "solid:\{color_revision(color)}"
Brush::LinearGradient(spec) =>
"linear:\{spec.start.x},\{spec.start.y},\{spec.end.x},\{spec.end.y},\{color_revision(spec.start_color)},\{color_revision(spec.end_color)}"
Brush::RadialGradient(spec) =>
"radial:\{spec.center.x},\{spec.center.y},\{spec.radius},\{color_revision(spec.center_color)},\{color_revision(spec.edge_color)}"
}
}
///|
fn role_label(role : SemanticsRole) -> String {
match role {
None => "none"
Group => "group"
Text => "text"
Button => "button"
TextField => "text-field"
Checkbox => "checkbox"
Grid => "grid"
ScrollView => "scroll-view"
Switch => "switch"
Radio => "radio"
Slider => "slider"
Progress => "progress"
Tab => "tab"
List => "list"
ListItem => "list-item"
Dialog => "dialog"
Tooltip => "tooltip"
Menu => "menu"
ButtonGroup => "button-group"
Form => "form"
FormField => "form-field"
Tree => "tree"
TreeItem => "tree-item"
WebView => "web-view"
}
}
///|
fn action_label(action : SemanticsAction) -> String {
match action {
Activate => "activate"
Focus => "focus"
SetText => "set-text"
Submit => "submit"
Scroll => "scroll"
Select => "select"
Dismiss => "dismiss"
Expand => "expand"
Collapse => "collapse"
}
}
///|
fn main_axis_alignment_label(alignment : MainAxisAlignment) -> String {
match alignment {
MainStart => "start"
MainCenter => "center"
MainEnd => "end"
SpaceAround => "space-around"
SpaceBetween => "space-between"
SpaceEvenly => "space-evenly"
}
}
///|
fn cross_axis_alignment_label(alignment : CrossAxisAlignment) -> String {
match alignment {
CrossStart => "start"
CrossCenter => "center"
CrossEnd => "end"
CrossStretch => "stretch"
}
}