Add optional outline to rectangles
This commit is contained in:
parent
2ec24af92a
commit
2e4d961676
6 changed files with 145 additions and 68 deletions
|
@ -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 }
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
35
src/app.rs
35
src/app.rs
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
64
src/style.rs
64
src/style.rs
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
src/types.rs
32
src/types.rs
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue