diff --git a/apps/magpie/src/service/gl.rs b/apps/magpie/src/service/gl.rs index 689b340..38396ec 100644 --- a/apps/magpie/src/service/gl.rs +++ b/apps/magpie/src/service/gl.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later use canary::{DrawCommand, Vec2, PX_PER_MM}; -use glium::Surface; +use glium::{program::ProgramCreationInput, Surface}; #[derive(Copy, Clone)] pub struct Vertex { @@ -59,9 +59,21 @@ pub struct Graphics { impl Graphics { pub fn new(display: glium::Display) -> Self { - let program = - glium::Program::from_source(&display, VERTEX_SHADER_SRC, FRAGMENT_SHADER_SRC, None) - .unwrap(); + let program = glium::Program::new( + &display, + ProgramCreationInput::SourceCode { + vertex_shader: VERTEX_SHADER_SRC, + tessellation_control_shader: None, + tessellation_evaluation_shader: None, + geometry_shader: None, + fragment_shader: FRAGMENT_SHADER_SRC, + transform_feedback_varyings: None, + outputs_srgb: true, // don't automatically apply gamma correction + uses_point_size: false, + }, + ) + .unwrap(); + Self { display, program } } diff --git a/apps/sandbox/src/main.rs b/apps/sandbox/src/main.rs index 9ec1f57..5b54f7b 100644 --- a/apps/sandbox/src/main.rs +++ b/apps/sandbox/src/main.rs @@ -33,6 +33,7 @@ struct App { last_update: Instant, protocol_buf: String, bind_message_buf: String, + panel_bg: egui::Color32, } impl App { @@ -49,6 +50,7 @@ impl App { last_update: Instant::now(), protocol_buf: String::new(), bind_message_buf: String::new(), + panel_bg: egui::Color32::TRANSPARENT, } } } @@ -58,6 +60,8 @@ impl eframe::App for App { ctx.request_repaint(); egui::SidePanel::left("left_panel").show(ctx, |ui| { + ui.heading("New Panel"); + ui.label("Protocol name:"); ui.text_edit_singleline(&mut self.protocol_buf); @@ -81,6 +85,14 @@ impl eframe::App for App { self.panels.push(panel); } + + ui.separator(); + ui.heading("Global Settings"); + + ui.horizontal(|ui| { + ui.label("Panel background color: "); + ui.color_edit_button_srgba(&mut self.panel_bg); + }); }); let dt = self.last_update.elapsed().as_secs_f32(); @@ -88,7 +100,7 @@ impl eframe::App for App { for panel in self.panels.iter_mut() { panel.panel.update(dt); - panel.show(ctx); + panel.show(self.panel_bg, ctx); } } } @@ -102,83 +114,87 @@ pub struct PanelWindow { } impl PanelWindow { - pub fn show(&mut self, ctx: &egui::Context) { + pub fn show(&mut self, bg: egui::Color32, ctx: &egui::Context) { + let frame = egui::Frame::window(&ctx.style()).fill(bg); let window_id = egui::Id::new(format!("panel_{}", self.index)); - egui::Window::new("Panel").id(window_id).show(ctx, |ui| { - egui::menu::bar(ui, |ui| { - ui.checkbox(&mut self.show_msg, "Show Message Editor"); - }); + egui::Window::new("Panel") + .frame(frame) + .id(window_id) + .show(ctx, |ui| { + egui::menu::bar(ui, |ui| { + ui.checkbox(&mut self.show_msg, "Show Message Editor"); + }); - let sense = egui::Sense { - click: true, - drag: true, - focusable: true, - }; - - let desired_size = ui.available_size(); - let response = ui.allocate_response(desired_size, sense); - let rect = response.rect; - - if rect.size() != self.current_size { - let size = rect.size(); - self.current_size = size; - - let size = canary::Vec2::new(size.x, size.y); - self.panel.on_resize(size * PX_PER_MM); - } - - if let Some(hover_pos) = response.hover_pos() { - let local = (hover_pos - rect.left_top()) * PX_PER_MM; - let pos = canary::Vec2::new(local.x, local.y); - - let kind = if response.drag_started() { - CursorEventKind::Select - } else if response.drag_released() { - CursorEventKind::Deselect - } else if response.dragged() { - CursorEventKind::Drag - } else { - CursorEventKind::Hover + let sense = egui::Sense { + click: true, + drag: true, + focusable: true, }; - self.panel.on_cursor_event(kind, pos); - } + let desired_size = ui.available_size(); + let response = ui.allocate_response(desired_size, sense); + let rect = response.rect; - let texture = egui::TextureId::Managed(0); - let uv = egui::pos2(0.0, 0.0); - let mut mesh = egui::Mesh::with_texture(texture); + if rect.size() != self.current_size { + let size = rect.size(); + self.current_size = size; - let commands = self.panel.draw(); - for command in commands.into_iter() { - let voff = mesh.vertices.len() as u32; - - match command { - canary::DrawCommand::Mesh { vertices, indices } => { - for v in vertices.iter() { - use egui::epaint::Vertex; - let pos = v.position / PX_PER_MM; - let pos = egui::pos2(pos.x, pos.y); - let pos = pos + rect.left_top().to_vec2(); - let (r, g, b, a) = v.color.to_rgba_unmultiplied(); - let color = egui::Color32::from_rgba_unmultiplied(r, g, b, a); - let v = Vertex { pos, uv, color }; - mesh.vertices.push(v); - } - - for i in indices.iter() { - mesh.indices.push(i + voff); - } - } - _ => unimplemented!(), + let size = canary::Vec2::new(size.x, size.y); + self.panel.on_resize(size * PX_PER_MM); } - } - let painter = ui.painter_at(rect); - let shape = egui::Shape::mesh(mesh); - painter.add(shape); + if let Some(hover_pos) = response.hover_pos() { + let local = (hover_pos - rect.left_top()) * PX_PER_MM; + let pos = canary::Vec2::new(local.x, local.y); - response - }); + let kind = if response.drag_started() { + CursorEventKind::Select + } else if response.drag_released() { + CursorEventKind::Deselect + } else if response.dragged() { + CursorEventKind::Drag + } else { + CursorEventKind::Hover + }; + + self.panel.on_cursor_event(kind, pos); + } + + let texture = egui::TextureId::Managed(0); + let uv = egui::pos2(0.0, 0.0); + let mut mesh = egui::Mesh::with_texture(texture); + + let commands = self.panel.draw(); + for command in commands.into_iter() { + let voff = mesh.vertices.len() as u32; + + match command { + canary::DrawCommand::Mesh { vertices, indices } => { + for v in vertices.iter() { + use egui::epaint::Vertex; + let pos = v.position / PX_PER_MM; + let pos = egui::pos2(pos.x, pos.y); + let pos = pos + rect.left_top().to_vec2(); + let (r, g, b, a) = v.color.to_rgba_unmultiplied(); + let color = egui::Color32::from_rgba_unmultiplied(r, g, b, a); + let v = Vertex { pos, uv, color }; + mesh.vertices.push(v); + } + + for i in indices.iter() { + mesh.indices.push(i + voff); + } + } + _ => unimplemented!(), + } + } + + let painter = ui.painter_at(rect); + let shape = egui::Shape::mesh(mesh); + painter.add(shape); + + response + }); let msg_edit_id = egui::Id::new(format!("msg_edit_{}", self.index)); egui::Window::new("Message Editor") diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index c2ada0e..58c0266 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -173,13 +173,13 @@ impl Color { ) } - pub fn alpha_multiply(&self, mul: u8) -> Self { + pub const fn alpha_multiply(&self, mul: u8) -> Self { let a = self.0 as u8 as u16; let multiplied = ((a * (mul as u16)) >> 8) as u8; self.with_alpha(multiplied) } - pub fn with_alpha(&self, alpha: u8) -> Self { + pub const fn with_alpha(&self, alpha: u8) -> Self { Self(self.0 & 0xffffff00 | alpha as u32) } diff --git a/scripts/sao-ui/src/lib.rs b/scripts/sao-ui/src/lib.rs index b558d3f..948c2e9 100644 --- a/scripts/sao-ui/src/lib.rs +++ b/scripts/sao-ui/src/lib.rs @@ -7,6 +7,7 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub mod anim; pub mod main_menu; pub mod music_player; +pub mod style; pub mod widgets; use api::*; @@ -23,6 +24,7 @@ fn bind_panel_impl(panel: Panel, protocol: Message, msg: Message) -> Box MusicPlayerPanel::bind(panel, msg), + "wip-dialog" => ConfirmationDialogPanel::bind(panel, msg), _ => MainMenuPanel::bind(panel, msg), } } @@ -50,15 +52,23 @@ impl PanelImpl for ConfirmationDialogPanel { self.dialog.on_cursor_event(kind, at.into()); } - fn on_resize(&mut self, _size: Vec2) {} + fn on_resize(&mut self, size: Vec2) { + self.dialog.resize(size); + } fn on_message(&mut self, _msg: Message) {} } impl ConfirmationDialogPanel { pub fn bind(panel: Panel, msg: Message) -> Box { - let msg = msg.to_vec(); - let info: DialogInfo = serde_json::from_slice(&msg).unwrap(); + // let msg = msg.to_vec(); + // let info: DialogInfo = serde_json::from_slice(&msg).unwrap(); + + let info = DialogInfo { + title: "Hello world!".to_string(), + content: "Testing, testing...".to_string(), + responses: vec![DialogResponse::Yes, DialogResponse::No], + }; use widgets::dialog::*; let style = DialogStyle::default(); diff --git a/scripts/sao-ui/src/main_menu.rs b/scripts/sao-ui/src/main_menu.rs index 87bbbb0..8ae2206 100644 --- a/scripts/sao-ui/src/main_menu.rs +++ b/scripts/sao-ui/src/main_menu.rs @@ -64,9 +64,9 @@ impl Default for MainMenu { radius: 7.5, spacing: 1.5, thickness: 0.4, - body_color: Color::WHITE, - ring_color: Color::WHITE, - icon_color: Color::BLACK, + body_color: THEME.palette.surface, + ring_color: THEME.palette.surface, + icon_color: THEME.palette.text, }; let mut buttons = Vec::new(); @@ -150,6 +150,7 @@ pub struct PlayerInfo { width: f32, height: f32, rounding: f32, + color: Color, } impl PlayerInfo { @@ -158,6 +159,7 @@ impl PlayerInfo { width: 70.0, height: 120.0, rounding: 5.0, + color: THEME.palette.surface, } } } @@ -170,7 +172,7 @@ impl RectBounds for PlayerInfo { impl Widget for PlayerInfo { fn draw(&mut self, ctx: &DrawContext) { - ctx.draw_rounded_rect(self.get_bounds(), self.rounding, Color::WHITE); + ctx.draw_rounded_rect(self.get_bounds(), self.rounding, self.color); } } diff --git a/scripts/sao-ui/src/music_player.rs b/scripts/sao-ui/src/music_player.rs index 9eab50e..f0e190c 100644 --- a/scripts/sao-ui/src/music_player.rs +++ b/scripts/sao-ui/src/music_player.rs @@ -179,7 +179,8 @@ impl MusicPlayerWidget { text: content.to_string(), }; - let label = Label::new_centered(text, 10.0, Color::BLACK); + let color = style.body.text_color; + let label = Label::new_centered(text, 10.0, color); Offset::new(label, Vec2::ZERO) }; @@ -196,7 +197,7 @@ impl MusicPlayerWidget { text, HorizontalAlignment::Center, scale, - Color::BLACK, + THEME.palette.text, 0.0, 0.0, baseline, @@ -225,18 +226,18 @@ impl MusicPlayerWidget { radius: style.footer.height * 0.3, spacing: style.footer.height * 0.1, thickness: style.footer.height * 0.025, - body_color: Color(0xf1b841ff), - ring_color: Color(0xf1b841ff), - icon_color: Color::BLACK, + body_color: THEME.palette.yellow, + ring_color: THEME.palette.yellow, + icon_color: THEME.palette.black, }; let secondary_button = RoundButtonStyle { radius: style.footer.height * 0.25, spacing: style.footer.height * 0.05, thickness: style.footer.height * 0.025, - body_color: Color::WHITE, - ring_color: Color::BLACK, - icon_color: Color::BLACK, + body_color: style.footer.color, + ring_color: THEME.palette.black, + icon_color: THEME.palette.black, }; let prev = RoundButton::new(secondary_button.clone(), Some(prev_text)); diff --git a/scripts/sao-ui/src/style.rs b/scripts/sao-ui/src/style.rs new file mode 100644 index 0000000..b111dad --- /dev/null +++ b/scripts/sao-ui/src/style.rs @@ -0,0 +1,92 @@ +use canary_script::Color; + +/// A reusable set of colors. Used by default widget styles. +pub struct Palette { + pub base: Color, + pub base_hover: Color, + pub base_active: Color, + pub surface: Color, + pub overlay: Color, + pub text: Color, + pub black: Color, + pub red: Color, + pub green: Color, + pub yellow: Color, + pub blue: Color, + pub magenta: Color, + pub cyan: Color, + pub white: Color, +} + +/// Sword Art Online color palette. +pub const SAO_PALETTE: Palette = Palette { + base: Color::WHITE.with_alpha(0xa0), + base_hover: Color::WHITE.with_alpha(0xd0), + base_active: Color::YELLOW, + surface: Color::WHITE, + overlay: Color::WHITE, + text: Color::BLACK, + black: Color::BLACK, + red: Color::RED, + green: Color::GREEN, + yellow: Color::YELLOW, + blue: Color::BLUE, + magenta: Color::MAGENTA, + cyan: Color::CYAN, + white: Color::WHITE, +}; + +/// Rose Pine color palette. +pub const ROSE_PINE_PALETTE: Palette = Palette { + base: Color(0x191724ff), + base_hover: Color(0x21202eff), // Highlight Low + base_active: Color(0x403d52ff), // Highlight Med + surface: Color(0x1f1d2eff), + overlay: Color(0x26233aff), + text: Color(0xe0def4ff), + black: Color(0x6e6a86ff), // Muted + red: Color(0xeb6f92ff), // Love + green: Color(0x7fb59fff), // ??? (not in Rose Pine?) + yellow: Color(0xf6c177ff), // Gold + blue: Color(0x31748fff), // Pine + magenta: Color(0xc4a7e7ff), // Iris + cyan: Color(0x9ccfd8ff), // Foam + white: Color(0xe0def4ff), // Text +}; + +/// Rose Pine Moon color palette. +pub const ROSE_PINE_MOON_PALETTE: Palette = Palette { + base: Color(0x232136ff), + base_hover: Color(0x2a283eff), // Highlight Low + base_active: Color(0x44415aff), // Highlight Med + surface: Color(0x2a273fff), + overlay: Color(0x393552), + text: Color(0xe0def4ff), + black: Color(0x6e6a86ff), // Muted + red: Color(0xeb6f92ff), // Love + green: Color(0x7fb59fff), // ??? (not in Rose Pine?) + yellow: Color(0xf6c177ff), // Gold + blue: Color(0x3e8fb0ff), // Pine + magenta: Color(0xc4a7e7ff), // Iris + cyan: Color(0x9ccfd8ff), // Foam + white: Color(0xe0def4ff), // Text +}; + +/// Common measurements for widget shapes. +pub struct Metrics { + pub surface_rounding: f32, +} + +/// Common default parameters for widget styles. +pub struct Theme { + pub palette: Palette, + pub metrics: Metrics, +} + +/// The global theme. +pub const THEME: Theme = Theme { + palette: ROSE_PINE_MOON_PALETTE, + metrics: Metrics { + surface_rounding: 5.0, + }, +}; diff --git a/scripts/sao-ui/src/widgets/button.rs b/scripts/sao-ui/src/widgets/button.rs index d2b8859..846eae5 100644 --- a/scripts/sao-ui/src/widgets/button.rs +++ b/scripts/sao-ui/src/widgets/button.rs @@ -118,6 +118,8 @@ pub struct RectButtonStyle { pub inactive_color: Color, pub hover_color: Color, pub selected_color: Color, + pub icon_color: Color, + pub label_color: Color, } impl Default for RectButtonStyle { @@ -129,9 +131,11 @@ impl Default for RectButtonStyle { label_baseline: 0.25, icon_scale_factor: 0.8, icon_margin_factor: 1.1, - inactive_color: Color::WHITE.with_alpha(0x40), - hover_color: Color::WHITE.with_alpha(0xb0), - selected_color: Color::YELLOW, + inactive_color: THEME.palette.base, + hover_color: THEME.palette.base_hover, + selected_color: THEME.palette.base_active, + icon_color: THEME.palette.black, + label_color: THEME.palette.text, } } } @@ -168,7 +172,7 @@ impl RectButton { label_left += margin; alignment = HorizontalAlignment::Left; let scale = rect.height() * style.icon_scale_factor; - let color = Color::BLACK; + let color = style.icon_color; let cx = rect.tl.x + margin / 2.0; let cy = rect.tl.y + rect.height() / 2.0; let center = Vec2::new(cx, cy); @@ -182,7 +186,7 @@ impl RectButton { let right = rect.br.x; let baseline = rect.tl.y; let baseline = (rect.height() * (1.0 - style.label_baseline)) + baseline; - let color = Color::BLACK; + let color = style.label_color; Label::new(text, alignment, scale, color, left, right, baseline) }); diff --git a/scripts/sao-ui/src/widgets/dialog.rs b/scripts/sao-ui/src/widgets/dialog.rs index f8dea23..f954984 100644 --- a/scripts/sao-ui/src/widgets/dialog.rs +++ b/scripts/sao-ui/src/widgets/dialog.rs @@ -23,8 +23,8 @@ impl DialogResponse { pub fn get_color(&self) -> Color { match self { - DialogResponse::Yes => Color::BLUE, - DialogResponse::No => Color::RED, + DialogResponse::Yes => THEME.palette.blue, + DialogResponse::No => THEME.palette.red, } } } @@ -40,7 +40,7 @@ pub struct DialogStyle { impl Default for DialogStyle { fn default() -> Self { Self { - rounding: 5.0, + rounding: THEME.metrics.surface_rounding, header: Default::default(), body: Default::default(), footer: Default::default(), @@ -61,10 +61,10 @@ pub struct DialogHeaderStyle { impl Default for DialogHeaderStyle { fn default() -> Self { Self { - color: Color::WHITE, + color: THEME.palette.surface, height: 20.0, text_font: Font::new(crate::DISPLAY_FONT), - text_color: Color::BLACK, + text_color: THEME.palette.text, text_scale_factor: 0.65, text_baseline: 0.25, } @@ -82,9 +82,9 @@ pub struct DialogBodyStyle { impl Default for DialogBodyStyle { fn default() -> Self { Self { - color: Color::WHITE.with_alpha(0xb0), + color: THEME.palette.base, text_font: Font::new(crate::CONTENT_FONT), - text_color: Color::BLACK, + text_color: THEME.palette.text, text_size: 5.0, } } @@ -95,6 +95,7 @@ pub struct DialogFooterStyle { pub icon_font: Font, pub button_radius: f32, pub color: Color, + pub button_fg: Color, pub height: f32, } @@ -103,7 +104,8 @@ impl Default for DialogFooterStyle { Self { icon_font: Font::new(crate::ICON_FONT), button_radius: 7.5, - color: Color::WHITE, + color: THEME.palette.surface, + button_fg: THEME.palette.white, height: 15.0, } } @@ -136,7 +138,7 @@ impl Dialog { thickness: radius * 0.05, body_color: color, ring_color: color, - icon_color: Color::WHITE, + icon_color: style.footer.button_fg, }; let text = LabelText { diff --git a/scripts/sao-ui/src/widgets/menu.rs b/scripts/sao-ui/src/widgets/menu.rs index 5c0a022..36f0ba7 100644 --- a/scripts/sao-ui/src/widgets/menu.rs +++ b/scripts/sao-ui/src/widgets/menu.rs @@ -224,6 +224,7 @@ pub struct TabMenu { impl TabMenu { const HEAD_RADIUS: f32 = 5.0; const HEAD_HEIGHT: f32 = 15.0; + const HEAD_COLOR: Color = THEME.palette.surface; const TAB_WIDTH: f32 = 15.0; const TAB_HEIGHT: f32 = 25.0; const TAB_NUM: usize = 6; @@ -235,9 +236,9 @@ impl TabMenu { radius: Self::HEAD_HEIGHT * 0.25, spacing: Self::HEAD_HEIGHT * 0.1, thickness: Self::HEAD_HEIGHT * 0.05, - body_color: Color::WHITE, - ring_color: Color::BLACK, - icon_color: Color::BLACK, + body_color: Self::HEAD_COLOR, + ring_color: THEME.palette.black, + icon_color: THEME.palette.black, }; const HEAD_BUTTON_MARGIN: f32 = Self::HEAD_HEIGHT / 2.0; @@ -348,20 +349,18 @@ impl Container for TabMenu { } fn draw(&mut self, ctx: &DrawContext) { - let head_color = Color::WHITE; - ctx.draw_partially_rounded_rect( CornerFlags::BOTTOM_RIGHT, self.separator_rect, Self::INNER_RADIUS, - head_color, + Self::HEAD_COLOR, ); ctx.draw_partially_rounded_rect( CornerFlags::TOP, self.head_rect, Self::HEAD_RADIUS, - head_color, + Self::HEAD_COLOR, ); } } diff --git a/scripts/sao-ui/src/widgets/mod.rs b/scripts/sao-ui/src/widgets/mod.rs index a9968df..fee8899 100644 --- a/scripts/sao-ui/src/widgets/mod.rs +++ b/scripts/sao-ui/src/widgets/mod.rs @@ -75,6 +75,7 @@ impl Widget for T { pub mod prelude { pub use super::*; pub use crate::anim::Animation; + pub use crate::style::{self, THEME}; pub use canary_script::{*, api::*}; pub use keyframe::functions::*; } diff --git a/scripts/sao-ui/src/widgets/scroll.rs b/scripts/sao-ui/src/widgets/scroll.rs index 130cbc5..0925313 100644 --- a/scripts/sao-ui/src/widgets/scroll.rs +++ b/scripts/sao-ui/src/widgets/scroll.rs @@ -21,11 +21,11 @@ impl Default for ScrollBarStyle { margin: Vec2::splat(2.0), body_radius: 1.0, body_width: 3.0, - body_idle_color: Color(0x7f7f7fff), - body_hover_color: Color(0xb0b0b0ff), - body_selected_color: Color::WHITE, + body_idle_color: THEME.palette.base, + body_hover_color: THEME.palette.base_hover, + body_selected_color: THEME.palette.base_active, rail_width: 1.0, - rail_color: Color(0xa0a0a07f), + rail_color: THEME.palette.base, } } }