SAO UI button icons + Label widget

This commit is contained in:
mars 2022-07-21 13:42:18 -06:00
parent b7df68195a
commit 2e36ef9851
4 changed files with 189 additions and 59 deletions

View File

@ -12,7 +12,3 @@ glam = "^0.21"
keyframe = "1"
canary_script = { path = "../script", features = ["glam"] }
wee_alloc = "^0.4"
[profile.release]
opt-level = 3
lto = "fat"

View File

@ -1,6 +1,7 @@
use canary_script::Panel;
use crate::{Color, Vec2};
use bitflags::bitflags;
use canary_script::GlyphPosition;
use canary_script::Panel;
pub enum Corner {
TopRight,
@ -81,6 +82,13 @@ impl Rect {
}
}
pub fn scale(&self, scale: f32) -> Self {
Self {
bl: self.bl * scale,
tr: self.tr * scale,
}
}
pub fn is_valid(&self) -> bool {
self.bl.cmplt(self.tr).all()
}
@ -122,6 +130,15 @@ impl Rect {
}
}
impl From<canary_script::Rect> for Rect {
fn from(other: canary_script::Rect) -> Self {
Self {
bl: other.bl.into(),
tr: other.tr.into(),
}
}
}
pub struct DrawContext {
panel: Panel,
offset: Option<Vec2>,
@ -163,7 +180,8 @@ impl DrawContext {
color.a *= opacity;
}
self.panel.draw_triangle(v1.into(), v2.into(), v3.into(), color);
self.panel
.draw_triangle(v1.into(), v2.into(), v3.into(), color);
}
pub fn draw_circle(&self, center: Vec2, radius: f32, color: Color) {
@ -312,6 +330,24 @@ impl DrawContext {
self.draw_rect(inner_rect, color);
}
pub fn draw_text(
&self,
glyphs: &[GlyphPosition],
mut offset: Vec2,
scale: f32,
mut color: Color,
) {
if let Some(delta) = self.offset {
offset += delta;
}
if let Some(opacity) = self.opacity {
color.a *= opacity;
}
self.panel.draw_glyphs(glyphs, offset.into(), scale, color);
}
pub fn get_clip_rect(&self) -> &Option<Rect> {
&self.clip_rect
}

View File

@ -34,24 +34,6 @@ impl PanelImpl for DummyPanel {
fn draw(&mut self) {
let ctx = draw::DrawContext::new(self.panel);
self.menu.draw(&ctx);
let font = Font::new("Iosevka Nerd Font");
let text = "";
let bg = Color::MAGENTA;
let fg = Color::BLACK;
let scale = 0.1;
let pos = Vec2::ZERO;
let layout = TextLayout::new(&font, text);
let bounds = layout.get_bounds();
let bounds = draw::Rect {
bl: Vec2::from(bounds.bl) * scale,
tr: Vec2::from(bounds.tr) * scale,
};
ctx.draw_rect(bounds.offset(pos), bg);
let glyphs = layout.get_glyphs();
self.panel.draw_glyphs(&glyphs, pos.into(), scale, fg);
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: canary_script::Vec2) {

View File

@ -1,12 +1,13 @@
use crate::anim::Animation;
use crate::draw::{CornerFlags, DrawContext, Rect};
use crate::{Color, CursorEventKind, Vec2};
use canary_script::{Font, GlyphPosition, TextLayout};
use keyframe::functions::*;
pub trait Widget {
fn update(&mut self, dt: f32);
fn draw(&mut self, ctx: &DrawContext);
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2);
fn update(&mut self, dt: f32) {}
fn draw(&mut self, ctx: &DrawContext) {}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {}
}
pub trait Button {
@ -25,26 +26,132 @@ pub enum ButtonState {
Releasing,
}
pub struct RoundButton {
pub pos: Vec2,
pub radius: f32,
pub spacing: f32,
pub thickness: f32,
pub shrink_anim: Animation<EaseOutQuint>,
pub was_clicked: bool,
pub state: ButtonState,
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum HorizontalAlignment {
Left,
Center,
Right,
}
impl Default for RoundButton {
fn default() -> Self {
pub struct LabelText {
pub font: Font,
pub text: String,
}
pub struct Label {
text: LabelText,
alignment: HorizontalAlignment,
scale: f32,
color: Color,
left: f32,
right: f32,
baseline: f32,
center_y: bool,
dirty: bool,
glyphs: Vec<GlyphPosition>,
bounds: Rect,
offset: Vec2,
}
impl Label {
pub fn new(
text: LabelText,
alignment: HorizontalAlignment,
scale: f32,
color: Color,
left: f32,
right: f32,
baseline: f32,
center_y: bool,
) -> Self {
Self {
text,
alignment,
scale,
color,
left,
right,
baseline,
center_y,
dirty: true,
glyphs: Vec::new(),
bounds: Rect::from_xy_size(Vec2::ZERO, Vec2::ZERO),
offset: Vec2::ZERO,
}
}
}
impl Widget for Label {
fn draw(&mut self, ctx: &DrawContext) {
if self.dirty {
let layout = TextLayout::new(&self.text.font, &self.text.text);
let bounds = Rect::from(layout.get_bounds()).scale(self.scale);
self.bounds = bounds;
self.glyphs = layout.get_glyphs();
let xoff = match self.alignment {
HorizontalAlignment::Left => self.left - bounds.bl.x,
HorizontalAlignment::Right => self.right - bounds.tr.x,
HorizontalAlignment::Center => {
let available = self.right - self.left;
let halfway = available / 2.0 + self.left;
let width = bounds.tr.x - bounds.bl.x;
let left = halfway - width / 2.0;
left - bounds.bl.x
}
};
let yoff = if self.center_y {
(bounds.bl.y - bounds.tr.y) / 2.0 - bounds.bl.y
} else {
0.0
};
self.offset = Vec2::new(xoff, yoff + self.baseline);
self.dirty = false;
}
ctx.draw_text(&self.glyphs, self.offset, self.scale, self.color);
}
}
pub struct RoundButton {
pos: Vec2,
radius: f32,
spacing: f32,
thickness: f32,
shrink_anim: Animation<EaseOutQuint>,
was_clicked: bool,
state: ButtonState,
label: Option<Label>,
}
impl RoundButton {
pub fn new(label: Option<LabelText>) -> Self {
let radius = 0.05;
let label = label.map(|text| {
let scale = radius * 1.5;
let alignment = HorizontalAlignment::Center;
let left = -radius;
let right = radius;
let baseline = 0.0;
let color = Color::BLACK;
let center_y = true;
Label::new(
text, alignment, scale, color, left, right, baseline, center_y,
)
});
Self {
pos: Default::default(),
radius: 0.02,
spacing: 0.005,
thickness: 0.001,
radius,
spacing: 0.01,
thickness: 0.002,
shrink_anim: Animation::new(EaseOutQuint, 0.1, 1.0, 0.0),
was_clicked: false,
state: ButtonState::Idle,
label,
}
}
}
@ -72,6 +179,10 @@ impl Widget for RoundButton {
let spacing = self.shrink_anim.get() * self.spacing;
ctx.draw_circle(self.pos, self.radius, color);
ctx.draw_ring(self.pos, self.radius + spacing, self.thickness, color);
if let Some(label) = self.label.as_mut() {
label.draw(ctx);
}
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {
@ -714,14 +825,22 @@ pub struct MainMenu {
}
impl MainMenu {
pub const SUBMENU_SPACING: f32 = 0.05;
pub const SUBMENU_SPACING: f32 = 0.1;
}
impl Default for MainMenu {
fn default() -> Self {
let icon_font = Font::new("Iosevka Nerd Font");
let icons = ["", "", "", "", ""];
let mut buttons = Vec::new();
for _ in 0..5 {
let button = RoundButton::default();
for icon in icons {
let text = LabelText {
font: icon_font,
text: icon.to_string(),
};
let button = RoundButton::new(Some(text));
buttons.push(button);
}
@ -730,7 +849,7 @@ impl Default for MainMenu {
let inventory = Reveal::new(inventory, -0.02, 0.1);
Self {
menu: ScrollMenu::new(buttons, 0.1),
menu: ScrollMenu::new(buttons, 0.2),
inventory,
}
}
@ -773,14 +892,14 @@ pub struct TabMenu {
}
impl TabMenu {
const HEAD_RADIUS: f32 = 0.025;
const HEAD_HEIGHT: f32 = 0.03;
const TAB_WIDTH: f32 = 0.04;
const TAB_HEIGHT: f32 = 0.06;
const HEAD_RADIUS: f32 = 0.05;
const HEAD_HEIGHT: f32 = 0.1;
const TAB_WIDTH: f32 = 0.1;
const TAB_HEIGHT: f32 = 0.15;
const TAB_NUM: usize = 6;
const SEPARATOR_WIDTH: f32 = 0.015;
const INNER_RADIUS: f32 = 0.005;
const CONTENT_WIDTH: f32 = 0.4;
const SEPARATOR_WIDTH: f32 = 0.02;
const INNER_RADIUS: f32 = 0.01;
const CONTENT_WIDTH: f32 = 0.64;
pub fn new() -> Self {
let tab_size = Vec2::new(Self::TAB_WIDTH, Self::TAB_HEIGHT);
@ -901,7 +1020,7 @@ pub struct Inventory {
impl Inventory {
pub fn new(available_width: f32) -> (Self, f32) {
let height = 1.5;
let height = 1.28;
(
Self {
@ -914,25 +1033,22 @@ impl Inventory {
}
impl Widget for Inventory {
fn update(&mut self, dt: f32) {}
fn draw(&mut self, ctx: &DrawContext) {
let box_size = 0.04;
let box_margin = 0.01;
let box_size = 0.06;
let box_margin = 0.02;
let box_stride = box_size + box_margin;
let grid_width = (self.width / box_stride).floor() as usize;
let grid_height = (self.height / box_stride).floor() as usize;
let grid_offset = Vec2::new(box_margin, box_margin) / 2.0;
for x in 0..grid_width {
for y in 0..grid_height {
let off = Vec2::new(x as f32, y as f32) * box_stride;
let off = Vec2::new(x as f32, y as f32) * box_stride + grid_offset;
let rect = Rect::from_xy_size(off, Vec2::new(box_size, box_size));
let color = Color::MAGENTA;
ctx.draw_rect(rect, color);
}
}
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {}
}