use iced::widget::{
button, canvas, checkbox, column, container, mouse_area, pick_list, progress_bar, row, slider,
text, text_input, toggler, Stack,
};
use iced::{
alignment, border, mouse, Border, Center, Color, Element, Fill, Length, Pixels, Point,
Rectangle, Renderer, Size, Task, Theme,
};
use rae::{
CupertinoPresentationKind, CupertinoPresentationSurface, CupertinoSegmentedControl,
CupertinoSourceListItem, CupertinoSourceListModel, CupertinoToolbarItem,
CupertinoToolbarItemKind, CupertinoTrafficLight, CupertinoTrafficLightKind,
CupertinoWindowChrome, Rect, Rgba, TabItem,
};
pub fn main() -> iced::Result {
iced::application(
"rae cupertino showcase",
CupertinoShowcase::update,
CupertinoShowcase::view,
)
.theme(CupertinoShowcase::theme)
.window_size(Size::new(1360.0, 860.0))
.run_with(|| (CupertinoShowcase::new(), Task::none()))
}
struct CupertinoShowcase {
chrome: CupertinoWindowChrome,
source_list: CupertinoSourceListModel,
segmented: CupertinoSegmentedControl,
presentation: CupertinoPresentationSurface,
search: String,
hovered_toolbar_id: Option<String>,
active_project_id: String,
mode: DetailMode,
accent: f32,
material_depth: f32,
sidebar_visible: bool,
inspector_visible: bool,
sheet_open: bool,
reduce_transparency: bool,
live_preview: bool,
}
#[derive(Debug, Clone)]
enum Message {
SearchChanged(String),
ToolbarHovered(String),
ToolbarExited(String),
ToolbarPressed(String),
SourceSelected(String),
SegmentSelected(String),
ModeSelected(DetailMode),
AccentChanged(f32),
MaterialDepthChanged(f32),
ReduceTransparencyChanged(bool),
LivePreviewChanged(bool),
ToggleSheet,
}
impl CupertinoShowcase {
fn new() -> Self {
let mut chrome = CupertinoWindowChrome::new("main", "Rae Design Studio")
.with_subtitle("Cupertino-quality renderer-neutral surfaces")
.with_rect(Rect::new(0.0, 0.0, 1180.0, 740.0));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"sidebar",
"Sidebar",
CupertinoToolbarItemKind::SidebarToggle,
));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"back-forward",
"Back Forward",
CupertinoToolbarItemKind::Navigation,
));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"search",
"Search",
CupertinoToolbarItemKind::Search,
));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"mode",
"Mode",
CupertinoToolbarItemKind::Segment,
));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"status",
"Synced",
CupertinoToolbarItemKind::Status,
));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"share",
"Share",
CupertinoToolbarItemKind::Action,
));
chrome.push_toolbar_item(CupertinoToolbarItem::new(
"account",
"TK",
CupertinoToolbarItemKind::Account,
));
let source_list = CupertinoSourceListModel::new(
"source-list",
[
CupertinoSourceListItem::new("today", "Today")
.with_symbol("Now")
.with_badge("8")
.with_accent(Rgba::rgb8(122, 185, 255)),
CupertinoSourceListItem::new("desktop", "Desktop")
.with_subtitle("Window chrome, toolbar, sheets")
.with_symbol("Mac")
.with_badge("12")
.with_accent(Rgba::rgb8(152, 132, 255)),
CupertinoSourceListItem::new("controls", "Controls")
.with_subtitle("Buttons, search, segments")
.with_symbol("UI")
.with_badge("24")
.with_accent(Rgba::rgb8(90, 226, 180)),
CupertinoSourceListItem::new("materials", "Materials")
.with_subtitle("Vibrancy, depth, glow")
.with_symbol("Mat")
.with_badge("16")
.with_accent(Rgba::rgb8(255, 188, 96)),
CupertinoSourceListItem::new("review", "Review")
.with_subtitle("Contrast, focus, motion")
.with_symbol("QA")
.with_badge("5")
.with_accent(Rgba::rgb8(255, 112, 158)),
CupertinoSourceListItem::new("agents", "Agents")
.with_subtitle("Tool calls, progress, status")
.with_symbol("AI")
.with_badge("18")
.with_accent(Rgba::rgb8(118, 244, 200)),
CupertinoSourceListItem::new("assets", "Assets")
.with_subtitle("Media grid, lightbox, previews")
.with_symbol("Img")
.with_badge("31")
.with_accent(Rgba::rgb8(255, 212, 120)),
CupertinoSourceListItem::new("audio", "Audio")
.with_subtitle("Meters, rack, automation")
.with_symbol("Mix")
.with_badge("14")
.with_accent(Rgba::rgb8(255, 128, 96)),
],
)
.with_rect(Rect::new(0.0, 0.0, 250.0, 640.0));
let segmented = CupertinoSegmentedControl::new(
"view-mode",
[
TabItem::new("overview", "Overview"),
TabItem::new("surfaces", "Surfaces"),
TabItem::new("motion", "Motion"),
TabItem::new("audit", "Audit"),
],
)
.with_rect(Rect::new(0.0, 0.0, 360.0, 32.0));
let presentation = CupertinoPresentationSurface::new(
"export-sheet",
"Export Design Surface",
CupertinoPresentationKind::Sheet,
)
.with_subtitle("Window-scoped, action-focused presentation")
.with_rect(Rect::new(0.0, 0.0, 460.0, 298.0))
.with_anchor_rect(Rect::new(930.0, 14.0, 42.0, 32.0))
.with_accent(Rgba::rgb8(122, 185, 255))
.with_vibrancy(0.82)
.with_detent(0.64);
Self {
chrome,
source_list,
segmented,
presentation,
search: String::new(),
hovered_toolbar_id: None,
active_project_id: "today".to_string(),
mode: DetailMode::Overview,
accent: 0.64,
material_depth: 0.72,
sidebar_visible: true,
inspector_visible: true,
sheet_open: false,
reduce_transparency: false,
live_preview: true,
}
}
fn update(&mut self, message: Message) {
match message {
Message::SearchChanged(value) => {
self.search = value;
self.source_list.set_query(&self.search);
if let Some(selected) = self.source_list.selected() {
self.active_project_id = selected.id.clone();
}
}
Message::ToolbarHovered(id) => self.hovered_toolbar_id = Some(id),
Message::ToolbarExited(id) => {
if self.hovered_toolbar_id.as_deref() == Some(id.as_str()) {
self.hovered_toolbar_id = None;
}
}
Message::ToolbarPressed(id) => match id.as_str() {
"sidebar" => self.sidebar_visible = !self.sidebar_visible,
"share" => self.sheet_open = true,
"account" => self.inspector_visible = !self.inspector_visible,
_ => {}
},
Message::SourceSelected(id) => {
if self.source_list.select(&id) {
self.active_project_id = id;
}
}
Message::SegmentSelected(id) => {
if self.segmented.set_active(&id) {
self.mode = match id.as_str() {
"surfaces" => DetailMode::Surfaces,
"motion" => DetailMode::Motion,
"audit" => DetailMode::Audit,
_ => DetailMode::Overview,
};
}
}
Message::ModeSelected(mode) => self.mode = mode,
Message::AccentChanged(value) => {
self.accent = value;
self.presentation.accent = self.accent_color();
}
Message::MaterialDepthChanged(value) => self.material_depth = value,
Message::ReduceTransparencyChanged(value) => self.reduce_transparency = value,
Message::LivePreviewChanged(value) => self.live_preview = value,
Message::ToggleSheet => self.sheet_open = !self.sheet_open,
}
}
fn view(&self) -> Element<'_, Message> {
let window = container(
column![self.titlebar(), self.window_content()]
.spacing(0)
.height(Fill),
)
.width(Fill)
.height(Fill)
.style(move |_| style::window_frame(self.material_depth));
let mut stack = Stack::new().push(window);
if self.sheet_open {
stack = stack.push(
container(self.sheet())
.width(Fill)
.height(Fill)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center)
.style(style::sheet_scrim),
);
}
container(stack.width(Fill).height(Fill))
.padding(22)
.style(style::desktop)
.into()
}
fn titlebar(&self) -> Element<'_, Message> {
let lights = row![
traffic_light(self.chrome.traffic_lights()[0]),
traffic_light(self.chrome.traffic_lights()[1]),
traffic_light(self.chrome.traffic_lights()[2]),
]
.spacing(8)
.align_y(Center);
let title = column![
text(self.chrome.title.clone()).size(13),
text(self.chrome.subtitle.clone()).size(10),
]
.spacing(1)
.align_x(Center)
.width(Fill);
let mut tool_cluster = row![].spacing(8).align_y(Center);
for item in self.chrome.toolbar_layout() {
tool_cluster = tool_cluster.push(self.toolbar_item(item));
}
container(
row![lights, title, tool_cluster]
.spacing(16)
.align_y(Center),
)
.height(Length::Fixed(self.chrome.titlebar_height_px))
.padding([10, 14])
.style(move |_| style::titlebar(self.reduce_transparency, self.chrome.focused))
.into()
}
fn toolbar_item<'a>(&'a self, item: CupertinoToolbarItem) -> Element<'a, Message> {
let hovered = self.hovered_toolbar_id.as_deref() == Some(item.id.as_str());
let id = item.id.clone();
let content: Element<'a, Message> = match item.kind {
CupertinoToolbarItemKind::Search => text_input("Search", &self.search)
.on_input(Message::SearchChanged)
.padding(7)
.width(Length::Fixed(184.0))
.into(),
CupertinoToolbarItemKind::Segment => self.segmented_control(),
CupertinoToolbarItemKind::Status => chip("Live", Rgba::rgb8(90, 226, 180)),
CupertinoToolbarItemKind::Navigation => row![toolbar_button("<"), toolbar_button(">")]
.spacing(4)
.into(),
CupertinoToolbarItemKind::Account => container(text("TK").size(12))
.width(Length::Fixed(34.0))
.height(Length::Fixed(30.0))
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center)
.style(move |_| style::avatar(hovered))
.into(),
_ => toolbar_button(item.label.clone()),
};
mouse_area(content)
.on_enter(Message::ToolbarHovered(id.clone()))
.on_exit(Message::ToolbarExited(id.clone()))
.on_press(Message::ToolbarPressed(id))
.interaction(mouse::Interaction::Pointer)
.into()
}
fn segmented_control(&self) -> Element<'_, Message> {
let mut row = row![].spacing(2).align_y(Center);
for slot in self.segmented.slots() {
let id = slot.id.clone();
row = row.push(
button(text(slot.label.clone()).size(12))
.padding([5, 10])
.style(move |theme, status| style::segment_button(theme, status, slot.active))
.on_press(Message::SegmentSelected(id)),
);
}
container(row)
.padding(2)
.style(style::segmented_shell)
.into()
}
fn window_content(&self) -> Element<'_, Message> {
self.main_stage()
}
fn main_stage(&self) -> Element<'_, Message> {
let active = self
.source_list
.selected()
.map(|item| item.title.as_str())
.unwrap_or("Today");
let mode = self
.segmented
.active()
.map(|segment| segment.label.as_str())
.unwrap_or("Overview");
let source_items = self
.source_list
.filtered_items()
.into_iter()
.map(|item| SceneSourceItem {
id: item.id.clone(),
title: item.title.clone(),
subtitle: item.subtitle.clone(),
badge: item.badge.clone().unwrap_or_default(),
accent: item.accent,
})
.collect();
let scene = canvas(CupertinoScene {
active_id: self.active_project_id.clone(),
active: active.to_string(),
mode: mode.to_string(),
source_items,
accent: self.accent_color(),
material_depth: self.material_depth,
reduce_transparency: self.reduce_transparency,
live_preview: self.live_preview,
sidebar_visible: self.sidebar_visible,
inspector_visible: self.inspector_visible,
presentation_title: self.presentation.title.clone(),
presentation_subtitle: self.presentation.subtitle.clone(),
})
.width(Fill)
.height(Fill);
let controls = container(
row![
row![chip(mode, self.accent_color()), pill(self.material_label())]
.spacing(8)
.align_y(Center),
pick_list(DetailMode::ALL, Some(self.mode), Message::ModeSelected)
.placeholder("Mode")
.padding(8),
column![
text("Depth").size(10),
progress_bar(0.0..=1.0, self.material_depth),
]
.spacing(4)
.width(Length::Fixed(170.0)),
slider(
0.0..=1.0,
self.material_depth,
Message::MaterialDepthChanged
)
.width(Length::Fixed(120.0)),
slider(0.0..=1.0, self.accent, Message::AccentChanged).width(Length::Fixed(150.0)),
checkbox("Reduce", self.reduce_transparency)
.on_toggle(Message::ReduceTransparencyChanged),
toggler(self.live_preview)
.label("Live")
.on_toggle(Message::LivePreviewChanged),
button("Open Sheet").on_press(Message::ToggleSheet),
row![
metric("Accent", format!("{:.0}%", self.accent * 100.0)),
metric("Glow", if self.live_preview { "live" } else { "off" }),
]
.spacing(8),
]
.spacing(12)
.align_y(Center),
)
.padding([12, 14])
.style(move |_| style::floating_control_bar(self.accent_color()));
let stage = Stack::new().push(scene).push(
container(controls)
.width(Fill)
.height(Fill)
.padding(22)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Bottom),
);
container(stage.width(Fill).height(Fill))
.width(Fill)
.height(Fill)
.style(style::stage)
.into()
}
fn sheet(&self) -> Element<'_, Message> {
container(
column![
container(text(""))
.width(Length::Fixed(52.0))
.height(Length::Fixed(4.0))
.style(style::sheet_handle),
text(self.presentation.title.clone()).size(24),
text(self.presentation.subtitle.clone()).size(13),
container(
column![
inspector_stat("Preset", "macOS studio dark"),
inspector_stat("Surface", self.active_project_id.as_str()),
inspector_stat("Renderer", "consumer-owned"),
]
.spacing(8),
)
.padding(12)
.style(move |_| style::sheet_summary(self.accent_color())),
row![
metric("Chrome", "ready"),
metric("Catalog", "synced"),
metric("Motion", "ok"),
]
.spacing(10),
row![
button("Cancel")
.style(button::secondary)
.on_press(Message::ToggleSheet),
button("Export").on_press(Message::ToggleSheet),
]
.spacing(8)
.align_y(Center),
]
.spacing(16),
)
.width(Length::Fixed(self.presentation.rect.width.max(320.0)))
.padding(22)
.style(move |_| style::sheet(self.presentation.accent))
.into()
}
fn accent_color(&self) -> Rgba {
Rgba::rgb8(
(90.0 + self.accent * 80.0) as u8,
(145.0 + self.accent * 80.0) as u8,
255,
)
}
fn material_label(&self) -> &'static str {
if self.reduce_transparency {
"solid"
} else if self.material_depth > 0.78 {
"liquid glass"
} else if self.material_depth > 0.48 {
"vibrant"
} else {
"quiet"
}
}
fn theme(&self) -> Theme {
Theme::Dark
}
}
fn traffic_light<'a>(light: CupertinoTrafficLight) -> Element<'a, Message> {
container(text(""))
.width(Length::Fixed(light.rect.width.max(12.0)))
.height(Length::Fixed(light.rect.height.max(12.0)))
.style(move |_| style::traffic_light(light.kind, light.color()))
.into()
}
fn toolbar_button<'a>(label: impl Into<String>) -> Element<'a, Message> {
container(text(label.into()).size(12))
.height(Length::Fixed(30.0))
.padding([6, 10])
.align_y(alignment::Vertical::Center)
.style(style::toolbar_button)
.into()
}
fn chip<'a>(label: impl Into<String>, accent: Rgba) -> Element<'a, Message> {
let label = label.into();
container(text(label).size(10))
.padding([4, 8])
.style(move |_| style::chip(accent))
.into()
}
fn pill<'a>(label: impl Into<String>) -> Element<'a, Message> {
container(text(label.into()).size(11))
.padding([7, 10])
.style(style::pill)
.into()
}
fn inspector_stat<'a>(label: impl Into<String>, value: impl Into<String>) -> Element<'a, Message> {
container(
row![
text(label.into()).size(10).width(Fill),
text(value.into()).size(12)
]
.spacing(8)
.align_y(Center),
)
.padding([7, 9])
.style(style::inspector_stat)
.into()
}
fn metric<'a>(label: impl Into<String>, value: impl Into<String>) -> Element<'a, Message> {
container(
column![text(label.into()).size(10), text(value.into()).size(16)]
.spacing(1)
.align_x(Center),
)
.width(Length::Fixed(82.0))
.padding(8)
.style(style::metric)
.into()
}
#[derive(Debug, Clone)]
struct SceneSourceItem {
id: String,
title: String,
subtitle: String,
badge: String,
accent: Rgba,
}
#[derive(Debug, Clone)]
struct CupertinoScene {
active_id: String,
active: String,
mode: String,
source_items: Vec<SceneSourceItem>,
accent: Rgba,
material_depth: f32,
reduce_transparency: bool,
live_preview: bool,
sidebar_visible: bool,
inspector_visible: bool,
presentation_title: String,
presentation_subtitle: String,
}
impl canvas::Program<Message> for CupertinoScene {
type State = ();
fn update(
&self,
_state: &mut Self::State,
event: canvas::Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> (canvas::event::Status, Option<Message>) {
if let canvas::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event {
if let Some(point) = cursor.position_in(bounds) {
let layout = scene_layout(
bounds.width,
bounds.height,
self.sidebar_visible,
self.inspector_visible,
);
if let Some(id) = hit_source_item(point, &layout, self.source_items.len()) {
return (
canvas::event::Status::Captured,
Some(Message::SourceSelected(self.source_items[id].id.clone())),
);
}
}
}
(canvas::event::Status::Ignored, None)
}
fn mouse_interaction(
&self,
_state: &Self::State,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> mouse::Interaction {
if let Some(point) = cursor.position_in(bounds) {
let layout = scene_layout(
bounds.width,
bounds.height,
self.sidebar_visible,
self.inspector_visible,
);
if hit_source_item(point, &layout, self.source_items.len()).is_some() {
return mouse::Interaction::Pointer;
}
}
mouse::Interaction::default()
}
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let mut frame = canvas::Frame::new(renderer, bounds.size());
let w = bounds.width.max(1.0);
let h = bounds.height.max(1.0);
let accent = scene_color(self.accent, 1.0);
let depth = self.material_depth.clamp(0.0, 1.0);
let alpha = if self.reduce_transparency { 1.0 } else { 0.86 };
let background = canvas::Path::rectangle(Point::ORIGIN, bounds.size());
frame.fill(
&background,
canvas::gradient::Linear::new(Point::ORIGIN, Point::new(w, h))
.add_stop(0.0, color8(7, 8, 13, 255))
.add_stop(0.42, color8(15, 22, 38, 255))
.add_stop(1.0, color8(4, 5, 8, 255)),
);
for (x, y, radius, opacity) in [
(w * 0.20, h * 0.22, 210.0, 0.22),
(w * 0.78, h * 0.18, 170.0, 0.16),
(w * 0.63, h * 0.78, 230.0, 0.13),
] {
let glow = canvas::Path::circle(Point::new(x, y), radius);
frame.fill(&glow, scene_color(self.accent, opacity));
}
draw_wallpaper_beams(&mut frame, w, h, self.accent, depth);
draw_grid(&mut frame, w, h, depth);
let layout = scene_layout(w, h, self.sidebar_visible, self.inspector_visible);
draw_shadow(
&mut frame,
layout.scene_x,
layout.scene_y,
layout.scene_w,
layout.scene_h,
32.0,
0.18,
);
let shell = rounded(
layout.scene_x,
layout.scene_y,
layout.scene_w,
layout.scene_h,
32.0,
);
frame.fill(
&shell,
canvas::gradient::Linear::new(
Point::new(layout.scene_x, layout.scene_y),
Point::new(
layout.scene_x + layout.scene_w,
layout.scene_y + layout.scene_h,
),
)
.add_stop(0.0, color8(35, 39, 51, (238.0 * alpha) as u8))
.add_stop(0.52, color8(18, 23, 34, (236.0 * alpha) as u8))
.add_stop(1.0, color8(10, 12, 18, (244.0 * alpha) as u8)),
);
frame.stroke(
&shell,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(185, 199, 230, 92)),
);
draw_deck_header(
&mut frame,
&layout,
&self.active,
&self.mode,
accent,
self.live_preview,
);
if self.sidebar_visible {
draw_source_list(&mut frame, &layout, &self.source_items, &self.active_id);
}
draw_workspace(
&mut frame,
layout.content_x,
layout.content_y,
layout.content_w.max(260.0),
layout.content_h,
&self.active,
&self.mode,
accent,
depth,
self.live_preview,
);
if self.inspector_visible {
draw_inspector(
&mut frame,
layout.inspector_x,
layout.content_y,
layout.inspector_w,
layout.content_h,
accent,
depth,
);
}
draw_floating_sheet_hint(
&mut frame,
layout.content_x + layout.content_w * 0.42,
layout.content_y + 22.0,
accent,
&self.presentation_title,
&self.presentation_subtitle,
);
vec![frame.into_geometry()]
}
}
#[derive(Clone, Copy, Debug)]
struct SceneLayout {
scene_x: f32,
scene_y: f32,
scene_w: f32,
scene_h: f32,
title_h: f32,
content_x: f32,
content_y: f32,
content_w: f32,
content_h: f32,
sidebar_w: f32,
inspector_x: f32,
inspector_w: f32,
}
fn scene_layout(
width: f32,
height: f32,
sidebar_visible: bool,
inspector_visible: bool,
) -> SceneLayout {
let scene_x = 22.0;
let scene_y = 18.0;
let scene_w = (width - 44.0).max(360.0);
let scene_h = (height - 94.0).max(320.0);
let title_h = 76.0;
let sidebar_w = if sidebar_visible { 226.0 } else { 0.0 };
let inspector_w = if inspector_visible { 266.0 } else { 0.0 };
let content_x = scene_x + sidebar_w;
let content_y = scene_y + title_h;
let content_h = scene_h - title_h;
let inspector_x = scene_x + scene_w - inspector_w;
let content_w = (scene_w - sidebar_w - inspector_w).max(260.0);
SceneLayout {
scene_x,
scene_y,
scene_w,
scene_h,
title_h,
content_x,
content_y,
content_w,
content_h,
sidebar_w,
inspector_x,
inspector_w,
}
}
fn hit_source_item(point: Point, layout: &SceneLayout, item_count: usize) -> Option<usize> {
if layout.sidebar_w <= 0.0 {
return None;
}
let x0 = layout.scene_x + 12.0;
let x1 = layout.scene_x + layout.sidebar_w - 12.0;
if point.x < x0 || point.x > x1 {
return None;
}
(0..item_count).find(|index| {
let row_y = layout.content_y + 54.0 + *index as f32 * 54.0;
point.y >= row_y && point.y <= row_y + 46.0
})
}
fn draw_wallpaper_beams(frame: &mut canvas::Frame, w: f32, h: f32, accent: Rgba, depth: f32) {
draw_text(
frame,
"RAE",
w * 0.70,
h * 0.20,
112.0,
color8(92, 116, 168, 22),
alignment::Horizontal::Center,
);
draw_text(
frame,
"LIQUID SYSTEM",
w * 0.19,
h * 0.88,
44.0,
color8(120, 180, 255, 20),
alignment::Horizontal::Center,
);
for index in 0..7 {
let y = h * (0.08 + index as f32 * 0.12);
let beam = canvas::Path::new(|path| {
path.move_to(Point::new(-40.0, y + index as f32 * 10.0));
path.bezier_curve_to(
Point::new(w * 0.20, y - 124.0),
Point::new(w * 0.61, y + 142.0),
Point::new(w + 54.0, y - 44.0),
);
});
frame.stroke(
&beam,
canvas::Stroke::default()
.with_width(0.8 + index as f32 * 0.22)
.with_color(scene_color(accent, 0.055 + depth * 0.035)),
);
}
for index in 0..6 {
let center = Point::new(w * (0.24 + index as f32 * 0.10), h * 0.38);
frame.stroke(
&canvas::Path::circle(center, 54.0 + index as f32 * 22.0),
canvas::Stroke::default().with_width(1.0).with_color(color8(
188,
208,
255,
10 + index as u8 * 3,
)),
);
}
let horizon = canvas::Path::new(|path| {
path.move_to(Point::new(w * 0.08, h * 0.74));
path.bezier_curve_to(
Point::new(w * 0.28, h * 0.55),
Point::new(w * 0.55, h * 0.92),
Point::new(w * 0.94, h * 0.64),
);
});
frame.stroke(
&horizon,
canvas::Stroke::default()
.with_width(5.0)
.with_color(scene_color(accent, 0.12 + depth * 0.08)),
);
}
fn draw_grid(frame: &mut canvas::Frame, w: f32, h: f32, depth: f32) {
let color = color8(118, 141, 190, (18.0 + depth * 24.0) as u8);
let stroke = canvas::Stroke::default().with_width(1.0).with_color(color);
let step = 42.0;
let mut x = 0.0;
while x < w {
frame.stroke(
&canvas::Path::line(Point::new(x, 0.0), Point::new(x, h)),
stroke,
);
x += step;
}
let mut y = 0.0;
while y < h {
frame.stroke(
&canvas::Path::line(Point::new(0.0, y), Point::new(w, y)),
stroke,
);
y += step;
}
}
fn draw_shadow(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
radius: f32,
opacity: f32,
) {
for index in 0..8 {
let spread = 8.0 + index as f32 * 4.0;
let path = rounded(
x - spread * 0.5,
y + 6.0 - spread * 0.5,
w + spread,
h + spread,
radius + spread * 0.4,
);
frame.fill(
&path,
Color::from_rgba(0.0, 0.0, 0.0, opacity / (index as f32 + 4.0)),
);
}
}
fn draw_toolbar_pill(frame: &mut canvas::Frame, x: f32, y: f32, w: f32, label: &str) {
let path = rounded(x, y, w, 28.0, 10.0);
frame.fill(&path, color8(19, 23, 33, 180));
frame.stroke(
&path,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(180, 194, 224, 58)),
);
draw_text(
frame,
label,
x + w * 0.5,
y + 7.0,
11.0,
color8(235, 241, 252, 210),
alignment::Horizontal::Center,
);
}
fn draw_deck_header(
frame: &mut canvas::Frame,
layout: &SceneLayout,
active: &str,
mode: &str,
accent: Color,
live_preview: bool,
) {
let x = layout.scene_x;
let y = layout.scene_y;
let w = layout.scene_w;
let header_h = layout.title_h;
let header = rounded(x + 10.0, y + 10.0, w - 20.0, header_h - 16.0, 24.0);
frame.fill(
&header,
canvas::gradient::Linear::new(
Point::new(x + 10.0, y + 10.0),
Point::new(x + w - 10.0, y + header_h),
)
.add_stop(0.0, color8(19, 27, 43, 218))
.add_stop(0.54, color8(12, 16, 25, 188))
.add_stop(1.0, Color { a: 0.24, ..accent }),
);
frame.stroke(
&header,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(169, 188, 226, 62)),
);
draw_text(
frame,
"SCRIN / RAE",
x + 32.0,
y + 24.0,
11.0,
color8(148, 166, 202, 222),
alignment::Horizontal::Left,
);
draw_text(
frame,
active,
x + 32.0,
y + 42.0,
25.0,
color8(248, 251, 255, 244),
alignment::Horizontal::Left,
);
draw_text(
frame,
format!("{mode} workspace · renderer-neutral state · canvas-owned composition"),
x + 245.0,
y + 28.0,
12.0,
color8(188, 202, 228, 218),
alignment::Horizontal::Left,
);
let status = if live_preview {
"live preview"
} else {
"paused"
};
draw_toolbar_pill(frame, x + w - 420.0, y + 26.0, 118.0, status);
draw_toolbar_pill(frame, x + w - 292.0, y + 26.0, 116.0, "blur 72");
draw_toolbar_pill(frame, x + w - 166.0, y + 26.0, 134.0, "publish-ready");
frame.fill(
&canvas::Path::circle(Point::new(x + w - 442.0, y + 40.0), 5.5),
Color { a: 0.88, ..accent },
);
}
fn draw_source_list(
frame: &mut canvas::Frame,
layout: &SceneLayout,
items: &[SceneSourceItem],
active_id: &str,
) {
let x = layout.scene_x;
let y = layout.content_y;
let w = layout.sidebar_w;
let h = layout.content_h;
let area = canvas::Path::rectangle(Point::new(x, y), Size::new(w, h));
frame.fill(
&area,
canvas::gradient::Linear::new(Point::new(x, y), Point::new(x + w, y + h))
.add_stop(0.0, color8(31, 35, 47, 196))
.add_stop(1.0, color8(15, 18, 25, 176)),
);
frame.stroke(
&canvas::Path::line(Point::new(x + w, y), Point::new(x + w, y + h)),
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(160, 174, 205, 46)),
);
draw_text(
frame,
"RAE SPACES",
x + 18.0,
y + 25.0,
10.0,
color8(150, 162, 184, 190),
alignment::Horizontal::Left,
);
draw_text(
frame,
"live surfaces · 96 examples",
x + w - 18.0,
y + 25.0,
9.0,
color8(112, 128, 158, 180),
alignment::Horizontal::Right,
);
for (index, item) in items.iter().enumerate() {
let row_y = y + 54.0 + index as f32 * 54.0;
let selected = item.id == active_id;
let row = rounded(x + 12.0, row_y, w - 24.0, 46.0, 13.0);
let accent = scene_color(item.accent, 1.0);
if selected {
frame.fill(
&rounded(x + 11.0, row_y + 5.0, 3.0, 36.0, 99.0),
Color { a: 0.96, ..accent },
);
frame.fill(
&row,
canvas::gradient::Linear::new(
Point::new(x + 12.0, row_y),
Point::new(x + w - 12.0, row_y + 46.0),
)
.add_stop(0.0, Color { a: 0.64, ..accent })
.add_stop(1.0, color8(42, 56, 88, 194)),
);
frame.stroke(
&row,
canvas::Stroke::default().with_width(1.0).with_color(accent),
);
}
frame.fill(
&rounded(x + 20.0, row_y + 12.0, 22.0, 22.0, 8.0),
Color { a: 0.20, ..accent },
);
frame.fill(
&canvas::Path::circle(Point::new(x + 31.0, row_y + 23.0), 7.0),
accent,
);
draw_text(
frame,
&item.title,
x + 52.0,
row_y + 9.0,
12.0,
color8(239, 244, 255, 230),
alignment::Horizontal::Left,
);
draw_text(
frame,
&item.subtitle,
x + 52.0,
row_y + 27.0,
9.0,
color8(157, 172, 204, 205),
alignment::Horizontal::Left,
);
frame.fill(
&rounded(x + w - 45.0, row_y + 13.0, 28.0, 20.0, 99.0),
color8(7, 10, 16, 110),
);
draw_text(
frame,
&item.badge,
x + w - 28.0,
row_y + 18.0,
11.0,
color8(204, 214, 232, 220),
alignment::Horizontal::Center,
);
}
let dock_y = y + h - 104.0;
draw_text(
frame,
"MATERIAL BUDGET",
x + 18.0,
dock_y,
10.0,
color8(150, 162, 184, 184),
alignment::Horizontal::Left,
);
for index in 0..4 {
let bar_x = x + 18.0 + index as f32 * 44.0;
frame.fill(
&rounded(bar_x, dock_y + 28.0, 32.0, 42.0, 10.0),
color8(12, 16, 24, 178),
);
frame.fill(
&rounded(
bar_x,
dock_y + 28.0 + index as f32 * 5.0,
32.0,
42.0 - index as f32 * 5.0,
10.0,
),
color8(94, 158, 255, 94 + index as u8 * 28),
);
}
}
#[allow(clippy::too_many_arguments)]
fn draw_workspace(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
active: &str,
mode: &str,
accent: Color,
depth: f32,
live_preview: bool,
) {
let inset = 22.0;
draw_text(
frame,
active,
x + inset,
y + 30.0,
34.0,
color8(246, 249, 255, 245),
alignment::Horizontal::Left,
);
draw_text(
frame,
format!("{mode} · liquid material preview"),
x + inset,
y + 72.0,
12.0,
color8(166, 180, 206, 215),
alignment::Horizontal::Left,
);
draw_context_tiles(frame, x + w - 362.0, y + 18.0, 332.0, accent, depth);
let hero_x = x + inset;
let hero_y = y + 118.0;
let hero_w = (w - inset * 2.0).max(220.0);
let hero_h = (h * 0.44).max(178.0);
let hero = rounded(hero_x, hero_y, hero_w, hero_h, 24.0);
frame.fill(
&hero,
canvas::gradient::Linear::new(
Point::new(hero_x, hero_y),
Point::new(hero_x + hero_w, hero_y + hero_h),
)
.add_stop(0.0, color8(39, 52, 82, 232))
.add_stop(
0.52,
Color {
a: 0.32 + depth * 0.30,
..accent
},
)
.add_stop(1.0, color8(11, 15, 24, 240)),
);
frame.stroke(
&hero,
canvas::Stroke::default()
.with_width(1.0)
.with_color(Color { a: 0.62, ..accent }),
);
draw_surface_topology(frame, hero_x, hero_y, hero_w, hero_h, accent, depth);
draw_layered_specimens(frame, hero_x, hero_y, hero_w, hero_h, accent, depth);
for index in 0..5 {
let radius = 34.0 + index as f32 * 19.0;
frame.stroke(
&canvas::Path::circle(
Point::new(hero_x + hero_w * 0.67, hero_y + hero_h * 0.52),
radius,
),
canvas::Stroke::default().with_width(1.0).with_color(Color {
a: 0.15 + index as f32 * 0.045,
..accent
}),
);
}
let wave = canvas::Path::new(|path| {
path.move_to(Point::new(hero_x + 28.0, hero_y + hero_h - 42.0));
path.bezier_curve_to(
Point::new(hero_x + hero_w * 0.22, hero_y + hero_h - 130.0),
Point::new(hero_x + hero_w * 0.52, hero_y + hero_h - 8.0),
Point::new(hero_x + hero_w - 28.0, hero_y + 58.0),
);
});
frame.stroke(
&wave,
canvas::Stroke::default()
.with_width(3.0)
.with_color(Color { a: 0.72, ..accent }),
);
draw_text(
frame,
"Surface Composer",
hero_x + 28.0,
hero_y + 24.0,
20.0,
color8(248, 251, 255, 242),
alignment::Horizontal::Left,
);
draw_text(
frame,
"traffic lights · source list · inspector · sheets",
hero_x + 28.0,
hero_y + 53.0,
11.0,
color8(205, 216, 236, 205),
alignment::Horizontal::Left,
);
let lower_y = hero_y + hero_h + 18.0;
let lower_h = (y + h - lower_y - 26.0).max(110.0);
let left_w = (hero_w * 0.56).max(260.0);
let right_w = (hero_w - left_w - 16.0).max(180.0);
draw_command_lens(
frame,
hero_x,
lower_y,
left_w,
lower_h,
accent,
live_preview,
);
draw_material_orbit(
frame,
hero_x + left_w + 16.0,
lower_y,
right_w,
lower_h,
accent,
depth,
);
}
fn draw_context_tiles(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
accent: Color,
depth: f32,
) {
let tile_w = ((w - 18.0) / 3.0).max(82.0);
for (index, (label, value, tint)) in [
("models", "118", color8(122, 185, 255, 255)),
("examples", "900+", color8(118, 244, 200, 255)),
("skins", "42", color8(255, 188, 96, 255)),
]
.into_iter()
.enumerate()
{
let tx = x + index as f32 * (tile_w + 9.0);
let tile = rounded(tx, y, tile_w, 58.0, 16.0);
frame.fill(
&tile,
canvas::gradient::Linear::new(Point::new(tx, y), Point::new(tx + tile_w, y + 58.0))
.add_stop(0.0, color8(19, 25, 38, 214))
.add_stop(
1.0,
Color {
a: 0.14 + depth * 0.12,
..tint
},
),
);
frame.stroke(
&tile,
canvas::Stroke::default().with_width(1.0).with_color(Color {
a: 0.24 + index as f32 * 0.08,
..accent
}),
);
draw_text(
frame,
label,
tx + 12.0,
y + 14.0,
9.0,
color8(158, 174, 205, 220),
alignment::Horizontal::Left,
);
draw_text(
frame,
value,
tx + 12.0,
y + 31.0,
18.0,
color8(248, 251, 255, 240),
alignment::Horizontal::Left,
);
frame.fill(
&canvas::Path::circle(Point::new(tx + tile_w - 17.0, y + 18.0), 5.0),
Color { a: 0.72, ..tint },
);
}
}
fn draw_surface_topology(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
accent: Color,
depth: f32,
) {
for index in 0..4 {
let strip_x = x + 34.0 + index as f32 * (w * 0.18);
let strip = canvas::Path::new(|path| {
path.move_to(Point::new(strip_x, y + h - 24.0));
path.bezier_curve_to(
Point::new(strip_x + w * 0.10, y + h * 0.62),
Point::new(strip_x - w * 0.03, y + h * 0.25),
Point::new(strip_x + w * 0.16, y + 18.0),
);
});
frame.stroke(
&strip,
canvas::Stroke::default()
.with_width(1.4 + index as f32 * 0.45)
.with_color(Color {
a: 0.14 + depth * 0.08,
..accent
}),
);
}
for index in 0..7 {
let t = index as f32 / 6.0;
let contour = canvas::Path::new(|path| {
let cy = y + h * (0.24 + t * 0.52);
path.move_to(Point::new(x + 24.0, cy));
path.bezier_curve_to(
Point::new(x + w * 0.28, cy - 42.0 + t * 30.0),
Point::new(x + w * 0.62, cy + 56.0 - t * 48.0),
Point::new(x + w - 28.0, cy - 12.0),
);
});
frame.stroke(
&contour,
canvas::Stroke::default().with_width(1.0).with_color(color8(
218,
232,
255,
24 + index as u8 * 4,
)),
);
}
let glass = canvas::Path::new(|path| {
path.move_to(Point::new(x + w * 0.58, y + 18.0));
path.line_to(Point::new(x + w - 26.0, y + 18.0));
path.line_to(Point::new(x + w * 0.77, y + h - 26.0));
path.line_to(Point::new(x + w * 0.47, y + h - 26.0));
path.close();
});
frame.fill(&glass, color8(255, 255, 255, 20));
frame.stroke(
&glass,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(255, 255, 255, 34)),
);
for index in 0..6 {
let px = x + w * (0.18 + index as f32 * 0.12);
let py = y + h * (0.72 - (index % 3) as f32 * 0.17);
frame.fill(
&canvas::Path::circle(Point::new(px, py), 3.8),
Color { a: 0.58, ..accent },
);
if index > 0 {
let prev_x = x + w * (0.18 + (index - 1) as f32 * 0.12);
let prev_y = y + h * (0.72 - ((index - 1) % 3) as f32 * 0.17);
frame.stroke(
&canvas::Path::line(Point::new(prev_x, prev_y), Point::new(px, py)),
canvas::Stroke::default()
.with_width(1.0)
.with_color(Color { a: 0.22, ..accent }),
);
}
}
}
fn draw_layered_specimens(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
accent: Color,
depth: f32,
) {
for (index, (label, detail, tint)) in [
("Glass", "blur 72", color8(122, 185, 255, 255)),
("Sheet", "detent .64", color8(118, 244, 200, 255)),
("Popover", "anchor", color8(255, 188, 96, 255)),
]
.into_iter()
.enumerate()
{
let card_w = (w * 0.22).clamp(132.0, 190.0);
let card_h = 70.0;
let card_x = x + w - card_w - 30.0 - index as f32 * 34.0;
let card_y = y + 42.0 + index as f32 * 54.0;
let card = rounded(card_x, card_y, card_w, card_h, 18.0);
frame.fill(
&card,
canvas::gradient::Linear::new(
Point::new(card_x, card_y),
Point::new(card_x + card_w, card_y + card_h),
)
.add_stop(0.0, color8(255, 255, 255, 32))
.add_stop(0.62, color8(22, 30, 45, 172))
.add_stop(
1.0,
Color {
a: 0.18 + depth * 0.16,
..tint
},
),
);
frame.stroke(
&card,
canvas::Stroke::default().with_width(1.0).with_color(color8(
232,
240,
255,
60 + index as u8 * 18,
)),
);
frame.fill(
&rounded(card_x + 12.0, card_y + 12.0, 34.0, 34.0, 12.0),
Color { a: 0.20, ..tint },
);
frame.fill(
&canvas::Path::circle(Point::new(card_x + 29.0, card_y + 29.0), 7.0),
Color { a: 0.76, ..tint },
);
draw_text(
frame,
label,
card_x + 56.0,
card_y + 17.0,
13.0,
color8(248, 251, 255, 236),
alignment::Horizontal::Left,
);
draw_text(
frame,
detail,
card_x + 56.0,
card_y + 39.0,
10.0,
color8(180, 196, 225, 210),
alignment::Horizontal::Left,
);
}
let spine = canvas::Path::new(|path| {
path.move_to(Point::new(x + w * 0.17, y + h * 0.28));
path.bezier_curve_to(
Point::new(x + w * 0.26, y + h * 0.16),
Point::new(x + w * 0.41, y + h * 0.80),
Point::new(x + w * 0.70, y + h * 0.72),
);
});
frame.stroke(
&spine,
canvas::Stroke::default()
.with_width(4.0)
.with_color(Color { a: 0.42, ..accent }),
);
}
fn draw_command_lens(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
accent: Color,
live_preview: bool,
) {
let shell = rounded(x, y, w, h, 22.0);
frame.fill(
&shell,
canvas::gradient::Linear::new(Point::new(x, y), Point::new(x + w, y + h))
.add_stop(0.0, color8(16, 22, 35, 226))
.add_stop(1.0, color8(8, 11, 18, 234)),
);
frame.stroke(
&shell,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(146, 166, 205, 72)),
);
draw_text(
frame,
"Command Lens",
x + 20.0,
y + 20.0,
17.0,
color8(246, 250, 255, 238),
alignment::Horizontal::Left,
);
draw_text(
frame,
if live_preview {
"live preview armed"
} else {
"preview paused"
},
x + w - 20.0,
y + 23.0,
10.0,
color8(170, 186, 214, 210),
alignment::Horizontal::Right,
);
let search = rounded(x + 18.0, y + 56.0, w - 36.0, 40.0, 14.0);
frame.fill(&search, color8(4, 7, 12, 184));
frame.stroke(
&search,
canvas::Stroke::default()
.with_width(1.0)
.with_color(Color { a: 0.46, ..accent }),
);
frame.fill(
&canvas::Path::circle(Point::new(x + 38.0, y + 76.0), 7.0),
Color { a: 0.28, ..accent },
);
draw_text(
frame,
"Search surfaces, effects, overlays",
x + 56.0,
y + 68.0,
12.0,
color8(194, 207, 232, 210),
alignment::Horizontal::Left,
);
let code_y = y + 116.0;
for index in 0..4 {
let row_y = code_y + index as f32 * 30.0;
let alpha = 120 + index as u8 * 22;
frame.fill(
&rounded(x + 18.0, row_y, w - 36.0, 20.0, 7.0),
color8(13, 18, 29, 150),
);
frame.fill(
&rounded(x + 31.0, row_y + 7.0, 38.0 + index as f32 * 12.0, 6.0, 99.0),
Color {
a: alpha as f32 / 255.0,
..accent
},
);
frame.fill(
&rounded(x + 82.0 + index as f32 * 8.0, row_y + 7.0, 86.0, 6.0, 99.0),
color8(160, 178, 214, 72),
);
frame.fill(
&rounded(x + w - 90.0, row_y + 7.0, 58.0, 6.0, 99.0),
color8(255, 188, 96, 94),
);
}
draw_dock(frame, x + 22.0, y + h - 54.0, w - 44.0, accent);
}
fn draw_material_orbit(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
accent: Color,
depth: f32,
) {
let shell = rounded(x, y, w, h, 22.0);
frame.fill(&shell, color8(12, 17, 28, 226));
frame.stroke(
&shell,
canvas::Stroke::default()
.with_width(1.0)
.with_color(Color { a: 0.38, ..accent }),
);
let center = Point::new(x + w * 0.50, y + h * 0.44);
for index in 0..6 {
let radius = 24.0 + index as f32 * 18.0;
frame.stroke(
&canvas::Path::circle(center, radius),
canvas::Stroke::default().with_width(1.0).with_color(Color {
a: 0.09 + depth * 0.055,
..accent
}),
);
}
for index in 0..9 {
let angle = index as f32 * 0.72 + depth * 1.4;
let radius = 38.0 + (index % 4) as f32 * 19.0;
let p = Point::new(
center.x + angle.cos() * radius,
center.y + angle.sin() * radius * 0.72,
);
frame.fill(
&canvas::Path::circle(p, 4.0 + (index % 3) as f32),
Color { a: 0.55, ..accent },
);
}
frame.fill(
&canvas::Path::circle(center, 28.0 + depth * 16.0),
Color {
a: 0.30 + depth * 0.28,
..accent
},
);
frame.stroke(
&canvas::Path::circle(center, 34.0 + depth * 16.0),
canvas::Stroke::default()
.with_width(2.0)
.with_color(Color { a: 0.74, ..accent }),
);
draw_text(
frame,
"Material Field",
x + 18.0,
y + 20.0,
17.0,
color8(246, 250, 255, 238),
alignment::Horizontal::Left,
);
draw_text(
frame,
"blur · glow · focus",
x + 18.0,
y + 46.0,
10.0,
color8(169, 184, 212, 208),
alignment::Horizontal::Left,
);
draw_text(
frame,
format!("{:.0}%", depth * 100.0),
x + w - 20.0,
y + h - 45.0,
28.0,
color8(248, 251, 255, 238),
alignment::Horizontal::Right,
);
draw_text(
frame,
"depth",
x + w - 20.0,
y + h - 22.0,
10.0,
color8(161, 176, 205, 205),
alignment::Horizontal::Right,
);
}
fn draw_dock(frame: &mut canvas::Frame, x: f32, y: f32, w: f32, accent: Color) {
let dock = rounded(x, y, w, 34.0, 16.0);
frame.fill(&dock, color8(4, 7, 12, 138));
frame.stroke(
&dock,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(180, 194, 224, 58)),
);
for index in 0..7 {
let cx = x + 24.0 + index as f32 * ((w - 48.0) / 6.0).max(18.0);
let size = 8.0 + (index % 3) as f32 * 2.0;
frame.fill(
&canvas::Path::circle(Point::new(cx, y + 17.0), size),
if index == 2 {
Color { a: 0.82, ..accent }
} else {
color8(126, 148, 190, 152)
},
);
}
}
fn draw_inspector(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
h: f32,
accent: Color,
depth: f32,
) {
let area = canvas::Path::rectangle(Point::new(x, y), Size::new(w, h));
frame.fill(&area, color8(20, 24, 34, 188));
draw_text(
frame,
"INSPECTOR",
x + 18.0,
y + 25.0,
10.0,
color8(148, 160, 184, 190),
alignment::Horizontal::Left,
);
draw_text(
frame,
"Material",
x + 18.0,
y + 52.0,
20.0,
color8(245, 248, 255, 238),
alignment::Horizontal::Left,
);
let sample = rounded(x + 18.0, y + 88.0, w - 36.0, 92.0, 18.0);
frame.fill(
&sample,
canvas::gradient::Linear::new(
Point::new(x + 18.0, y + 88.0),
Point::new(x + w - 18.0, y + 180.0),
)
.add_stop(0.0, Color { a: 0.76, ..accent })
.add_stop(1.0, color8(12, 16, 26, 230)),
);
frame.stroke(
&sample,
canvas::Stroke::default()
.with_width(1.0)
.with_color(Color { a: 0.88, ..accent }),
);
draw_text(
frame,
"liquid glass",
x + 34.0,
y + 114.0,
16.0,
color8(255, 255, 255, 232),
alignment::Horizontal::Left,
);
draw_text(
frame,
format!("quality {:.0}%", depth * 100.0),
x + 34.0,
y + 142.0,
11.0,
color8(225, 232, 246, 205),
alignment::Horizontal::Left,
);
draw_inspector_meters(frame, x + 18.0, y + 202.0, w - 36.0, accent, depth);
for (index, (label, value)) in [
("Shaders", "2"),
("Lights", "3"),
("Toolbar", "7"),
("Fallback", "solid"),
]
.into_iter()
.enumerate()
{
let row_y = y + 340.0 + index as f32 * 34.0;
let row = rounded(x + 18.0, row_y, w - 36.0, 28.0, 10.0);
frame.fill(&row, color8(12, 16, 25, 168));
draw_text(
frame,
label,
x + 30.0,
row_y + 7.0,
10.0,
color8(150, 164, 190, 215),
alignment::Horizontal::Left,
);
draw_text(
frame,
value,
x + w - 34.0,
row_y + 7.0,
11.0,
color8(238, 243, 253, 230),
alignment::Horizontal::Right,
);
}
}
fn draw_inspector_meters(
frame: &mut canvas::Frame,
x: f32,
y: f32,
w: f32,
accent: Color,
depth: f32,
) {
let panel = rounded(x, y, w, 112.0, 18.0);
frame.fill(&panel, color8(7, 11, 18, 168));
frame.stroke(
&panel,
canvas::Stroke::default()
.with_width(1.0)
.with_color(color8(160, 180, 220, 48)),
);
for (index, (label, amount)) in [("blur", depth), ("glow", 0.82), ("focus", 0.64)]
.into_iter()
.enumerate()
{
let row_y = y + 20.0 + index as f32 * 27.0;
draw_text(
frame,
label,
x + 14.0,
row_y - 2.0,
10.0,
color8(158, 174, 204, 210),
alignment::Horizontal::Left,
);
frame.fill(
&rounded(x + 64.0, row_y, w - 92.0, 8.0, 99.0),
color8(32, 38, 52, 190),
);
frame.fill(
&rounded(x + 64.0, row_y, (w - 92.0) * amount, 8.0, 99.0),
Color {
a: 0.48 + amount * 0.32,
..accent
},
);
frame.fill(
&canvas::Path::circle(Point::new(x + w - 16.0, row_y + 4.0), 4.0 + amount * 3.0),
Color { a: 0.46, ..accent },
);
}
let dial_center = Point::new(x + w - 40.0, y + 83.0);
frame.stroke(
&canvas::Path::circle(dial_center, 18.0),
canvas::Stroke::default()
.with_width(2.0)
.with_color(color8(136, 156, 198, 96)),
);
frame.stroke(
&canvas::Path::circle(dial_center, 11.0),
canvas::Stroke::default()
.with_width(2.0)
.with_color(Color { a: 0.72, ..accent }),
);
}
fn draw_floating_sheet_hint(
frame: &mut canvas::Frame,
x: f32,
y: f32,
accent: Color,
title: &str,
subtitle: &str,
) {
let path = rounded(x, y, 230.0, 74.0, 18.0);
draw_shadow(frame, x, y, 230.0, 74.0, 18.0, 0.18);
frame.fill(&path, color8(28, 33, 45, 230));
frame.stroke(
&path,
canvas::Stroke::default()
.with_width(1.0)
.with_color(Color { a: 0.48, ..accent }),
);
frame.fill(
&rounded(x + 86.0, y + 10.0, 58.0, 4.0, 99.0),
color8(215, 225, 244, 118),
);
draw_text(
frame,
title,
x + 22.0,
y + 26.0,
14.0,
color8(248, 251, 255, 232),
alignment::Horizontal::Left,
);
draw_text(
frame,
subtitle,
x + 22.0,
y + 48.0,
10.0,
color8(184, 197, 222, 205),
alignment::Horizontal::Left,
);
}
fn draw_text(
frame: &mut canvas::Frame,
content: impl Into<String>,
x: f32,
y: f32,
size: f32,
color: Color,
horizontal_alignment: alignment::Horizontal,
) {
frame.fill_text(canvas::Text {
content: content.into(),
position: Point::new(x, y),
color,
size: Pixels(size),
horizontal_alignment,
..Default::default()
});
}
fn rounded(x: f32, y: f32, width: f32, height: f32, radius: f32) -> canvas::Path {
canvas::Path::rounded_rectangle(
Point::new(x, y),
Size::new(width.max(0.0), height.max(0.0)),
radius.into(),
)
}
fn color8(r: u8, g: u8, b: u8, a: u8) -> Color {
Color {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: a as f32 / 255.0,
}
}
fn scene_color(color: Rgba, alpha: f32) -> Color {
let color = color.sanitized();
Color {
r: color.r,
g: color.g,
b: color.b,
a: (color.a * alpha).clamp(0.0, 1.0),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DetailMode {
Overview,
Surfaces,
Motion,
Audit,
}
impl DetailMode {
const ALL: [Self; 4] = [Self::Overview, Self::Surfaces, Self::Motion, Self::Audit];
}
impl std::fmt::Display for DetailMode {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::Overview => "Overview",
Self::Surfaces => "Surfaces",
Self::Motion => "Motion",
Self::Audit => "Audit",
})
}
}
mod style {
use super::*;
use iced::widget::{button, container};
pub fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Color {
Color {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: a as f32 / 255.0,
}
}
pub fn desktop(_theme: &Theme) -> container::Style {
container::Style {
text_color: Some(rgba8(238, 244, 255, 255)),
background: Some(rgba8(9, 11, 16, 255).into()),
..Default::default()
}
}
pub fn window_frame(depth: f32) -> container::Style {
let depth = depth.clamp(0.0, 1.0);
panel(
rgba8(18, 21, 29, (240.0 + depth * 12.0) as u8),
rgba8(150, 168, 205, (82.0 + depth * 68.0) as u8),
20.0,
)
}
pub fn titlebar(reduced: bool, focused: bool) -> container::Style {
let alpha = if reduced { 255 } else { 226 };
let border = if focused { 126 } else { 56 };
panel(rgba8(31, 35, 45, alpha), rgba8(166, 182, 214, border), 20.0)
}
pub fn stage(_theme: &Theme) -> container::Style {
container::Style {
text_color: Some(rgba8(235, 242, 255, 255)),
background: Some(rgba8(13, 16, 24, 236).into()),
..Default::default()
}
}
pub fn floating_control_bar(accent: Rgba) -> container::Style {
panel(rgba8(14, 18, 27, 222), with_alpha(accent, 118), 18.0)
}
pub fn sheet_scrim(_theme: &Theme) -> container::Style {
container::Style {
background: Some(rgba8(0, 0, 0, 92).into()),
..Default::default()
}
}
pub fn sheet(accent: Rgba) -> container::Style {
panel(rgba8(26, 30, 41, 250), with_alpha(accent, 182), 22.0)
}
pub fn sheet_handle(_theme: &Theme) -> container::Style {
container::Style {
background: Some(rgba8(198, 209, 230, 138).into()),
border: border::rounded(99.0),
..Default::default()
}
}
pub fn sheet_summary(accent: Rgba) -> container::Style {
panel(rgba8(17, 21, 31, 232), with_alpha(accent, 84), 14.0)
}
pub fn traffic_light(kind: CupertinoTrafficLightKind, color: Rgba) -> container::Style {
let border_color = match kind {
CupertinoTrafficLightKind::Close => rgba8(115, 24, 30, 150),
CupertinoTrafficLightKind::Minimize => rgba8(118, 78, 12, 150),
CupertinoTrafficLightKind::Zoom => rgba8(20, 94, 36, 150),
};
container::Style {
background: Some(iced_color(color).into()),
border: Border {
width: 1.0,
color: border_color,
radius: 99.0.into(),
},
..Default::default()
}
}
pub fn toolbar_button(_theme: &Theme) -> container::Style {
panel(rgba8(39, 44, 56, 208), rgba8(158, 174, 204, 74), 9.0)
}
pub fn avatar(hovered: bool) -> container::Style {
panel(
if hovered {
rgba8(48, 72, 122, 232)
} else {
rgba8(34, 45, 76, 218)
},
rgba8(148, 190, 255, if hovered { 150 } else { 88 }),
99.0,
)
}
pub fn segmented_shell(_theme: &Theme) -> container::Style {
panel(rgba8(14, 17, 25, 190), rgba8(150, 166, 198, 70), 10.0)
}
pub fn segment_button(theme: &Theme, status: button::Status, active: bool) -> button::Style {
if active {
button::primary(theme, status)
} else {
button::secondary(theme, status)
}
}
pub fn chip(accent: Rgba) -> container::Style {
panel(rgba8(32, 42, 70, 214), with_alpha(accent, 112), 99.0)
}
pub fn pill(_theme: &Theme) -> container::Style {
panel(rgba8(27, 33, 48, 218), rgba8(139, 157, 190, 78), 99.0)
}
pub fn metric(_theme: &Theme) -> container::Style {
panel(rgba8(25, 32, 49, 232), rgba8(132, 168, 235, 92), 14.0)
}
pub fn inspector_stat(_theme: &Theme) -> container::Style {
panel(rgba8(14, 18, 27, 178), rgba8(130, 146, 176, 48), 10.0)
}
fn panel(background: Color, border_color: Color, radius: f32) -> container::Style {
container::Style {
text_color: Some(rgba8(235, 242, 255, 255)),
background: Some(background.into()),
border: border::rounded(radius).color(border_color).width(1.0),
..Default::default()
}
}
fn with_alpha(color: Rgba, alpha: u8) -> Color {
let color = color.sanitized();
Color {
r: color.r,
g: color.g,
b: color.b,
a: alpha as f32 / 255.0,
}
}
fn iced_color(color: Rgba) -> Color {
let color = color.sanitized();
Color {
r: color.r,
g: color.g,
b: color.b,
a: color.a,
}
}
}