Files
galaxy/app/src/view_components/callout_bubble.rs
T

242 lines
8.1 KiB
Rust

use pathfinder_color::ColorU;
use warp_core::ui::theme::{phenomenon::PhenomenonStyle, Fill};
use warpui::elements::{
Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisSize,
MouseStateHandle, ParentElement, Radius, Stack,
};
use warpui::ui_components::checkbox::Checkbox;
use warpui::ui_components::components::UiComponentStyles;
use warpui::Element;
use crate::appearance::Appearance;
use crate::ui_components::icons::Icon;
/// Which direction the callout arrow points.
#[derive(Debug, Clone, Copy)]
pub enum CalloutArrowDirection {
Up,
Left,
}
/// Where the arrow is positioned along the bubble edge.
#[derive(Debug, Clone, Copy)]
pub enum CalloutArrowPosition {
/// Offset from the start of the bubble edge the arrow sits on.
/// For Up arrows: offset from the left edge.
/// For Left arrows: offset from the top edge.
Start(f32),
/// Offset from the end of the bubble edge the arrow sits on.
/// For Up arrows: offset from the right edge.
End(f32),
/// Centered on the bubble edge.
Center,
}
/// Configuration for rendering a callout bubble with an arrow.
pub struct CalloutBubbleConfig {
pub width: f32,
pub arrow_direction: CalloutArrowDirection,
pub arrow_position: CalloutArrowPosition,
}
pub fn phenomenon_background_color() -> ColorU {
PhenomenonStyle::background()
}
pub fn phenomenon_foreground_color() -> ColorU {
PhenomenonStyle::foreground()
}
pub fn phenomenon_accent_color() -> ColorU {
PhenomenonStyle::accent()
}
pub fn phenomenon_body_text_color() -> ColorU {
PhenomenonStyle::body_text()
}
pub fn phenomenon_label_text_color() -> ColorU {
PhenomenonStyle::label_text()
}
pub fn phenomenon_disabled_label_text_color() -> ColorU {
PhenomenonStyle::disabled_label_text()
}
pub fn phenomenon_subtle_border_color() -> ColorU {
PhenomenonStyle::subtle_border()
}
/// Returns the shared HOA callout background fill using the Phenomenon palette.
pub fn callout_background_fill(appearance: &Appearance) -> Fill {
let _ = appearance;
PhenomenonStyle::tinted_surface()
}
/// Returns the shared HOA callout border color using the Phenomenon palette.
pub fn callout_border_color(appearance: &Appearance) -> ColorU {
let _ = appearance;
PhenomenonStyle::surface_border()
}
/// Renders a callout bubble with an arrow indicator.
///
/// The bubble has an accent-tinted background with an accent border,
/// and a triangular arrow on the specified edge.
/// The `content` element is placed inside the bubble body.
pub fn render_callout_bubble(
content: Box<dyn Element>,
config: &CalloutBubbleConfig,
appearance: &Appearance,
) -> Box<dyn Element> {
let background = callout_background_fill(appearance);
let border_color = callout_border_color(appearance);
let bubble = ConstrainedBox::new(
Container::new(content)
.with_background(background)
.with_border(Border::all(1.).with_border_fill(Fill::Solid(border_color)))
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
.finish(),
)
.with_width(config.width)
.finish();
let (border_icon, fill_icon) = match config.arrow_direction {
CalloutArrowDirection::Up => (Icon::CalloutTriangleBorderUp, Icon::CalloutTriangleFillUp),
CalloutArrowDirection::Left => (
Icon::CalloutTriangleBorderLeft,
Icon::CalloutTriangleFillLeft,
),
};
let triangle = Stack::new()
.with_child(
ConstrainedBox::new(
border_icon
.to_warpui_icon(Fill::Solid(border_color))
.finish(),
)
.with_width(24.)
.with_height(24.)
.finish(),
)
.with_child(
ConstrainedBox::new(fill_icon.to_warpui_icon(background).finish())
.with_width(24.)
.with_height(24.)
.finish(),
)
.finish();
match config.arrow_direction {
CalloutArrowDirection::Up => {
let arrow_margin = match config.arrow_position {
CalloutArrowPosition::Start(offset) => {
Container::new(triangle).with_margin_left(offset)
}
CalloutArrowPosition::End(offset) => {
let margin_left = (config.width - offset - 24.).max(0.);
Container::new(triangle).with_margin_left(margin_left)
}
CalloutArrowPosition::Center => {
let margin_left = (config.width - 24.) / 2.;
Container::new(triangle).with_margin_left(margin_left)
}
};
let mut column = Flex::column().with_main_axis_size(MainAxisSize::Min);
column.add_child(arrow_margin.with_margin_bottom(-3.).finish());
column.add_child(bubble);
column.finish()
}
CalloutArrowDirection::Left => {
let (arrow_margin, cross_axis_alignment) = match config.arrow_position {
CalloutArrowPosition::Start(offset) => (
Container::new(triangle).with_margin_top(offset),
CrossAxisAlignment::Start,
),
CalloutArrowPosition::End(offset) => (
Container::new(triangle).with_margin_top(offset),
CrossAxisAlignment::Start,
),
CalloutArrowPosition::Center => {
(Container::new(triangle), CrossAxisAlignment::Center)
}
};
let mut row = Flex::row()
.with_main_axis_size(MainAxisSize::Min)
.with_cross_axis_alignment(cross_axis_alignment);
row.add_child(arrow_margin.with_margin_right(-3.).finish());
row.add_child(bubble);
row.finish()
}
}
}
/// Title text color for callout content (foreground, 100% opacity).
pub fn callout_title_color(appearance: &Appearance) -> ColorU {
let _ = appearance;
phenomenon_foreground_color()
}
/// Body/description text color for callout content in the Phenomenon palette.
pub fn callout_body_color(appearance: &Appearance) -> ColorU {
let _ = appearance;
phenomenon_body_text_color()
}
/// Label/secondary text color for callout content in the Phenomenon palette.
pub fn callout_label_color(appearance: &Appearance) -> ColorU {
let _ = appearance;
phenomenon_label_text_color()
}
/// Creates a checkbox styled for callout bubbles using the Phenomenon palette.
///
/// Unchecked: foreground border, no fill.
/// Checked: foreground fill, background-colored check icon.
pub fn callout_checkbox(
mouse_state: MouseStateHandle,
size: Option<f32>,
appearance: &Appearance,
) -> Checkbox {
let _ = appearance;
let foreground_color = phenomenon_foreground_color();
let foreground_fill = Fill::Solid(foreground_color);
let background_color = phenomenon_background_color();
let disabled_color = phenomenon_subtle_border_color();
let checkbox_size = size.or(Some(12.));
let corner_radius = CornerRadius::with_all(Radius::Pixels(2.));
Checkbox::new(
mouse_state,
UiComponentStyles {
font_size: checkbox_size,
border_color: Some(Fill::Solid(foreground_color).into()),
font_color: Some(foreground_color),
border_width: Some(1.),
border_radius: Some(corner_radius),
..Default::default()
},
None,
Some(UiComponentStyles {
font_size: checkbox_size,
background: Some(foreground_fill.into()),
border_color: Some(foreground_fill.into()),
font_color: Some(background_color),
border_radius: Some(corner_radius),
..Default::default()
}),
Some(UiComponentStyles {
font_size: checkbox_size,
border_color: Some(Fill::Solid(disabled_color).into()),
font_color: Some(disabled_color),
border_width: Some(1.),
border_radius: Some(corner_radius),
..Default::default()
}),
)
}