Add optional outline to rectangles

This commit is contained in:
Emil Ernerfeldt 2018-12-26 17:32:58 +01:00
parent 2ec24af92a
commit 2e4d961676
6 changed files with 145 additions and 68 deletions

View file

@ -15,18 +15,24 @@ function paintCommand(canvas, cmd) {
ctx.stroke(); ctx.stroke();
return; return;
case "circle": case "circle":
ctx.fillStyle = cmd.fill_style;
ctx.beginPath(); ctx.beginPath();
ctx.arc(cmd.center.x, cmd.center.y, cmd.radius, 0, 2 * Math.PI, false); ctx.arc(cmd.center.x, cmd.center.y, cmd.radius, 0, 2 * Math.PI, false);
ctx.fill(); if (cmd.fill_style) {
ctx.fillStyle = cmd.fill_style;
ctx.fill();
}
if (cmd.outline) {
ctx.lineWidth = cmd.outline.width;
ctx.strokeStyle = cmd.outline.style;
ctx.stroke();
}
return; return;
case "rounded_rect": case "rect":
ctx.fillStyle = cmd.fill_style;
var x = cmd.pos.x; var x = cmd.pos.x;
var y = cmd.pos.y; var y = cmd.pos.y;
var width = cmd.size.x; var width = cmd.size.x;
var height = cmd.size.y; var height = cmd.size.y;
var r = cmd.corner_radius; var r = Math.min(cmd.corner_radius, width / 2, height / 2);
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x + r, y); ctx.moveTo(x + r, y);
ctx.lineTo(x + width - r, y); ctx.lineTo(x + width - r, y);
@ -38,7 +44,15 @@ function paintCommand(canvas, cmd) {
ctx.lineTo(x, y + r); ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y); ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath(); ctx.closePath();
ctx.fill(); if (cmd.fill_style) {
ctx.fillStyle = cmd.fill_style;
ctx.fill();
}
if (cmd.outline) {
ctx.lineWidth = cmd.outline.width;
ctx.strokeStyle = cmd.outline.style;
ctx.stroke();
}
return; return;
case "text": case "text":
ctx.font = cmd.font; ctx.font = cmd.font;
@ -69,7 +83,7 @@ function js_gui(input) {
}); });
commands.push({ commands.push({
fillStyle: "#ff1111", fillStyle: "#ff1111",
kind: "rounded_rect", kind: "rect",
pos: { x: 100, y: 100 }, pos: { x: 100, y: 100 },
radius: 20, radius: 20,
size: { x: 200, y: 100 } size: { x: 200, y: 100 }

View file

@ -19,31 +19,39 @@ interface Line {
to: Vec2; to: Vec2;
} }
interface Outline {
width: number;
style: string;
}
interface Circle { interface Circle {
kind: "circle";
center: Vec2; center: Vec2;
fill_style: string; fill_style: string | null;
kind: "circle";
outline: Outline | null;
radius: number; radius: number;
} }
interface RoundedRect { interface Rect {
kind: "rounded_rect";
fill_style: string;
pos: Vec2;
corner_radius: number; corner_radius: number;
fill_style: string | null;
kind: "rect";
outline: Outline | null;
pos: Vec2;
size: Vec2; size: Vec2;
} }
interface Text { interface Text {
kind: "text"; kind: "text";
fill_style: string; fill_style: string | null;
font: string; font: string;
pos: Vec2; pos: Vec2;
stroke_style: string | null;
text: string; text: string;
text_align: "start" | "center" | "end"; text_align: "start" | "center" | "end";
} }
type PaintCmd = Clear | Line | Circle | RoundedRect | Text; type PaintCmd = Circle | Clear | Line | Rect | Text;
function paintCommand(canvas, cmd: PaintCmd) { function paintCommand(canvas, cmd: PaintCmd) {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
@ -66,19 +74,25 @@ function paintCommand(canvas, cmd: PaintCmd) {
return; return;
case "circle": case "circle":
ctx.fillStyle = cmd.fill_style;
ctx.beginPath(); ctx.beginPath();
ctx.arc(cmd.center.x, cmd.center.y, cmd.radius, 0, 2 * Math.PI, false); ctx.arc(cmd.center.x, cmd.center.y, cmd.radius, 0, 2 * Math.PI, false);
ctx.fill(); if (cmd.fill_style) {
ctx.fillStyle = cmd.fill_style;
ctx.fill();
}
if (cmd.outline) {
ctx.lineWidth = cmd.outline.width;
ctx.strokeStyle = cmd.outline.style;
ctx.stroke();
}
return; return;
case "rounded_rect": case "rect":
ctx.fillStyle = cmd.fill_style;
const x = cmd.pos.x; const x = cmd.pos.x;
const y = cmd.pos.y; const y = cmd.pos.y;
const width = cmd.size.x; const width = cmd.size.x;
const height = cmd.size.y; const height = cmd.size.y;
const r = cmd.corner_radius; const r = Math.min(cmd.corner_radius, width / 2, height / 2);
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x + r, y); ctx.moveTo(x + r, y);
ctx.lineTo(x + width - r, y); ctx.lineTo(x + width - r, y);
@ -90,7 +104,15 @@ function paintCommand(canvas, cmd: PaintCmd) {
ctx.lineTo(x, y + r); ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y); ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath(); ctx.closePath();
ctx.fill(); if (cmd.fill_style) {
ctx.fillStyle = cmd.fill_style;
ctx.fill();
}
if (cmd.outline) {
ctx.lineWidth = cmd.outline.width;
ctx.strokeStyle = cmd.outline.style;
ctx.stroke();
}
return; return;
case "text": case "text":
@ -149,7 +171,7 @@ function js_gui(input: RawInput): PaintCmd[] {
commands.push({ commands.push({
fillStyle: "#ff1111", fillStyle: "#ff1111",
kind: "rounded_rect", kind: "rect",
pos: { x: 100, y: 100 }, pos: { x: 100, y: 100 },
radius: 20, radius: 20,
size: { x: 200, y: 100 }, size: { x: 200, y: 100 },

View file

@ -1,12 +1,25 @@
use crate::gui::Gui; use crate::{gui::Gui, math::*, types::*};
#[derive(Default)]
pub struct App { pub struct App {
count: i32, count: i32,
slider_value: f32,
width: f32,
height: f32,
corner_radius: f32,
stroke_width: f32,
} }
impl App { impl App {
pub fn new() -> App {
App {
count: 0,
width: 100.0,
height: 50.0,
corner_radius: 5.0,
stroke_width: 2.0,
}
}
pub fn show_gui(&mut self, gui: &mut Gui) { pub fn show_gui(&mut self, gui: &mut Gui) {
if gui.button("Click me").clicked { if gui.button("Click me").clicked {
self.count += 1; self.count += 1;
@ -14,7 +27,21 @@ impl App {
gui.label(format!("The button have been clicked {} times", self.count)); gui.label(format!("The button have been clicked {} times", self.count));
gui.slider_f32("Slider", &mut self.slider_value, 0.0, 10.0); gui.slider_f32("width", &mut self.width, 0.0, 100.0);
gui.slider_f32("height", &mut self.height, 0.0, 100.0);
gui.slider_f32("corner_radius", &mut self.corner_radius, 0.0, 100.0);
gui.commands
.push(GuiCmd::PaintCommands(vec![PaintCmd::Rect {
corner_radius: self.corner_radius,
fill_style: Some("#888888ff".into()),
pos: vec2(300.0, 100.0),
size: vec2(self.width, self.height),
outline: Some(Outline {
width: self.stroke_width,
style: "#ffffffff".into(),
}),
}]));
let commands_json = format!("{:#?}", gui.gui_commands()); let commands_json = format!("{:#?}", gui.gui_commands());
gui.label(format!("All gui commands: {}", commands_json)); gui.label(format!("All gui commands: {}", commands_json));

View file

@ -42,7 +42,7 @@ pub fn show_gui(raw_input_json: &str) -> String {
let raw_input: RawInput = serde_json::from_str(raw_input_json).unwrap(); let raw_input: RawInput = serde_json::from_str(raw_input_json).unwrap();
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref APP: Mutex<app::App> = Default::default(); static ref APP: Mutex<app::App> = Mutex::new(app::App::new());
static ref LAST_INPUT: Mutex<RawInput> = Default::default(); static ref LAST_INPUT: Mutex<RawInput> = Default::default();
static ref GUI_STATE: Mutex<gui::GuiState> = Default::default(); static ref GUI_STATE: Mutex<gui::GuiState> = Default::default();
} }

View file

@ -3,6 +3,7 @@ use crate::{math::*, types::*};
/// TODO: a Style struct which defines colors etc /// TODO: a Style struct which defines colors etc
fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) { fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
match cmd { match cmd {
GuiCmd::PaintCommands(mut commands) => out_commands.append(&mut commands),
GuiCmd::Rect { GuiCmd::Rect {
rect, rect,
style, style,
@ -12,36 +13,19 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
let fill_style = if interact.active { let fill_style = if interact.active {
"#888888ff".to_string() "#888888ff".to_string()
} else if interact.hovered { } else if interact.hovered {
"#444444ff".to_string() "#666666ff".to_string()
} else { } else {
"#222222ff".to_string() "#444444ff".to_string()
}; };
out_commands.push(PaintCmd::RoundedRect { out_commands.push(PaintCmd::Rect {
corner_radius: 5.0, corner_radius: 5.0,
fill_style, fill_style: Some(fill_style),
outline: None,
pos: rect.pos, pos: rect.pos,
size: rect.size, size: rect.size,
}); });
} }
}, },
GuiCmd::Text {
pos,
text,
text_align,
style,
} => {
let fill_style = match style {
TextStyle::Button => "#ffffffbb".to_string(),
TextStyle::Label => "#ffffffbb".to_string(),
};
out_commands.push(PaintCmd::Text {
fill_style,
font: "14px Palatino".to_string(),
pos,
text,
text_align,
});
}
GuiCmd::Slider { GuiCmd::Slider {
interact, interact,
label, label,
@ -60,21 +44,23 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
let marker_fill_style = if interact.active { let marker_fill_style = if interact.active {
"#888888ff".to_string() "#888888ff".to_string()
} else if interact.hovered { } else if interact.hovered {
"#444444ff".to_string() "#666666ff".to_string()
} else { } else {
"#222222ff".to_string() "#444444ff".to_string()
}; };
out_commands.push(PaintCmd::RoundedRect { out_commands.push(PaintCmd::Rect {
corner_radius: 2.0, corner_radius: 2.0,
fill_style: "#111111ff".to_string(), fill_style: Some("#222222ff".to_string()),
outline: None,
pos: thin_rect.pos, pos: thin_rect.pos,
size: thin_rect.size, size: thin_rect.size,
}); });
out_commands.push(PaintCmd::RoundedRect { out_commands.push(PaintCmd::Rect {
corner_radius: 3.0, corner_radius: 3.0,
fill_style: marker_fill_style, fill_style: Some(marker_fill_style),
outline: None,
pos: marker_rect.pos, pos: marker_rect.pos,
size: marker_rect.size, size: marker_rect.size,
}); });
@ -82,9 +68,27 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, cmd: GuiCmd) {
out_commands.push(PaintCmd::Text { out_commands.push(PaintCmd::Text {
fill_style: "#ffffffbb".to_string(), fill_style: "#ffffffbb".to_string(),
font: "14px Palatino".to_string(), font: "14px Palatino".to_string(),
pos: rect.center(), pos: rect.min(),
text: format!("{}: {:.3}", label, value), text: format!("{}: {:.3}", label, value),
text_align: TextAlign::Center, text_align: TextAlign::Start,
});
}
GuiCmd::Text {
pos,
text,
text_align,
style,
} => {
let fill_style = match style {
TextStyle::Button => "#ffffffbb".to_string(),
TextStyle::Label => "#ffffffbb".to_string(),
};
out_commands.push(PaintCmd::Text {
fill_style,
font: "14px Palatino".to_string(),
pos,
text,
text_align,
}); });
} }
} }

View file

@ -104,17 +104,12 @@ pub enum TextStyle {
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub enum GuiCmd { pub enum GuiCmd {
PaintCommands(Vec<PaintCmd>),
Rect { Rect {
rect: Rect, rect: Rect,
style: RectStyle, style: RectStyle,
interact: InteractInfo, interact: InteractInfo,
}, },
Text {
pos: Vec2,
text: String,
text_align: TextAlign,
style: TextStyle,
},
Slider { Slider {
interact: InteractInfo, interact: InteractInfo,
label: String, label: String,
@ -123,24 +118,39 @@ pub enum GuiCmd {
rect: Rect, rect: Rect,
value: f32, value: f32,
}, },
Text {
pos: Vec2,
style: TextStyle,
text: String,
text_align: TextAlign,
},
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
pub type Style = String;
#[derive(Clone, Debug, Serialize)]
pub struct Outline {
pub width: f32,
pub style: Style,
}
#[derive(Clone, Debug, Serialize)] // TODO: copy #[derive(Clone, Debug, Serialize)] // TODO: copy
#[serde(rename_all = "snake_case", tag = "kind")] #[serde(rename_all = "snake_case", tag = "kind")]
pub enum PaintCmd { pub enum PaintCmd {
Clear { Clear {
fill_style: String, fill_style: Style,
}, },
RoundedRect { Rect {
fill_style: String, corner_radius: f32,
fill_style: Option<Style>,
outline: Option<Outline>,
pos: Vec2, pos: Vec2,
size: Vec2, size: Vec2,
corner_radius: f32,
}, },
Text { Text {
fill_style: String, fill_style: Style,
font: String, font: String,
pos: Vec2, pos: Vec2,
text: String, text: String,