Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/window-core/src/authoring/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub(super) struct ElementDto {
name: Option<String>,
frame: Option<ArtRefDto>,
art: Option<ArtRefDto>,
fill: Option<ArtRefDto>,
track: Option<ArtRefDto>,
text: Option<String>,
value: Option<String>,
initial: Option<String>,
Expand Down Expand Up @@ -74,6 +76,8 @@ struct ElementShapeDto {
kind: String,
frame: Option<ArtRefDto>,
art: Option<ArtRefDto>,
fill: Option<ArtRefDto>,
track: Option<ArtRefDto>,
text: Option<String>,
value: Option<String>,
initial: Option<String>,
Expand Down Expand Up @@ -144,6 +148,8 @@ impl ElementDto {
name: None,
frame: shape.frame,
art: shape.art,
fill: shape.fill,
track: shape.track,
text: shape.text,
value: shape.value,
initial: shape.initial,
Expand Down Expand Up @@ -191,6 +197,8 @@ impl ElementDto {
pub(super) fn intern_art(children: &mut [ElementDto], table: &mut Art) -> Result<()> {
for child in children {
intern(table, &mut child.frame, ArtUse::Frame)?;
intern(table, &mut child.fill, ArtUse::Frame)?;
intern(table, &mut child.track, ArtUse::Frame)?;
intern(table, &mut child.art, ArtUse::Image)?;
intern(table, &mut child.selected_sprite, ArtUse::Sprite)?;
intern_art(&mut child.children, table)?;
Expand Down
26 changes: 25 additions & 1 deletion crates/window-core/src/authoring/element/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ use crate::authoring::art::ArtRefDto;
use crate::authoring::flex::parse_auto_flow;
use crate::authoring::parse::validate_name;
use crate::inventory::{InventorySlotSection, SlotRectClaim};
use crate::model::{Element, FlexBox, Region, SlotSection, Switch, SwitchCase};
use crate::model::{Bar, Element, FlexBox, Region, SlotSection, Switch, SwitchCase};
use crate::{Error, Result};

/// The widest and tallest a bar may be: one font atlas page, which its largest fill glyph must fit.
const MAX_BAR_SIZE: u32 = 256;

impl ElementDto {
pub(in crate::authoring) fn into_element(self) -> Result<Element> {
let debug_name = self.debug_name.clone();
Expand All @@ -17,6 +20,7 @@ impl ElementDto {
match self.kind.as_str() {
"sprite" => self.build_sprite(),
"sprite_slot" => self.build_sprite_slot(),
"bar" => self.build_bar(),
"item" => self.build_item(),
"collection" => self.build_collection(),
"anvil_input" => self.build_anvil_input(),
Expand Down Expand Up @@ -51,6 +55,26 @@ impl ElementDto {
})
}

fn build_bar(self) -> Result<Element> {
let size = self.size()?;
if !(1..=MAX_BAR_SIZE).contains(&size.width) || !(1..=MAX_BAR_SIZE).contains(&size.height) {
return Err(Error::Validation(format!(
"bar element is {}x{} px; its `width` and `height` must each be from 1 to {MAX_BAR_SIZE}",
size.width, size.height
)));
}
let fill = art_name(&self.fill)?.ok_or_else(|| Error::Validation("bar element requires `fill`".into()))?;
Ok(Element::Bar(Box::new(Bar {
name: self.entry()?,
size,
pos: self.pos()?,
fill,
track: art_name(&self.track)?,
tint: self.tint()?,
debug_name: self.debug_name,
})))
}

fn build_item(self) -> Result<Element> {
Ok(Element::Item {
name: self.entry()?,
Expand Down
2 changes: 2 additions & 0 deletions crates/window-core/src/authoring/element/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{Error, Result, text_font};

const SPRITE_FIELDS: &[&str] = &["type", "art", "tint", "x", "y", "debug_name"];
const SPRITE_SLOT_FIELDS: &[&str] = &["type", "handle", "tint", "x", "y", "width", "height", "align", "debug_name"];
const BAR_FIELDS: &[&str] = &["type", "handle", "fill", "track", "tint", "x", "y", "width", "height", "debug_name"];
const ITEM_FIELDS: &[&str] = &["type", "handle", "slots", "pattern", "transform", "debug_name"];
const COLLECTION_FIELDS: &[&str] =
&["type", "handle", "frame", "selected_sprite", "slots", "pattern", "transform", "action", "debug_name"];
Expand Down Expand Up @@ -66,6 +67,7 @@ fn allowed_fields(kind: &str) -> Option<&'static [&'static str]> {
Some(match kind {
"sprite" => SPRITE_FIELDS,
"sprite_slot" => SPRITE_SLOT_FIELDS,
"bar" => BAR_FIELDS,
"item" => ITEM_FIELDS,
"collection" => COLLECTION_FIELDS,
"anvil_input" => ANVIL_INPUT_FIELDS,
Expand Down
12 changes: 9 additions & 3 deletions crates/window-core/src/authoring/element/handles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl Collector<'_> {
let role = match dto.kind.as_str() {
"slot" => Some(HandleRole::Slot),
"sprite_slot" => Some(HandleRole::SpriteSlot),
"bar" => Some(HandleRole::Bar),
"item" => Some(HandleRole::Item),
"collection" => Some(HandleRole::Collection),
"anvil_input" => Some(HandleRole::Input),
Expand All @@ -103,7 +104,8 @@ impl Collector<'_> {
Ok(())
}

/// Names an element that binds one handle: a text, sprite, or item slot, a collection, an input, or a switch.
/// Names an element that binds one handle: a text, sprite, or item slot, a bar, a collection, an input, or a
/// switch.
fn primary(&mut self, dto: &mut ElementDto, role: HandleRole, handle: &HandleRefDto) -> Result<()> {
let kind = self.declare(handle, role)?;
if role == HandleRole::Collection && dto.selected_sprite.is_some() && !handle.selectable {
Expand Down Expand Up @@ -216,11 +218,12 @@ impl Collector<'_> {
| HandleKind::Sprite
| HandleKind::Items
| HandleKind::Action
| HandleKind::Fraction
);
if !handle.shape.is_empty() && (!indexable || handle.shape.len() > 2 || handle.shape.contains(&0)) {
return Err(self.err(format!(
"{} `{id}` has an invalid shape; flags, values, text, sprites, items, and actions take `[n]` or \
`[rows, columns]` of positive sizes",
"{} `{id}` has an invalid shape; flags, values, text, sprites, items, actions, and fractions take \
`[n]` or `[rows, columns]` of positive sizes",
kind.id()
)));
}
Expand Down Expand Up @@ -357,6 +360,7 @@ fn parse_kind(kind: &str) -> Option<HandleKind> {
"input" => HandleKind::Input,
"builtin" => HandleKind::Builtin,
"tint" => HandleKind::Tint,
"fraction" => HandleKind::Fraction,
_ => return None,
})
}
Expand All @@ -372,6 +376,7 @@ fn role_accepts(role: HandleRole, kind: HandleKind) -> bool {
HandleRole::Switch => matches!(kind, K::Flag | K::Toggle | K::Value | K::Selection),
HandleRole::Click => matches!(kind, K::Action | K::Builtin | K::Selection | K::Toggle),
HandleRole::Tint => kind == K::Tint,
HandleRole::Bar => kind == K::Fraction,
}
}

Expand All @@ -385,5 +390,6 @@ fn role_noun(role: HandleRole) -> &'static str {
HandleRole::Switch => "a switch or show condition",
HandleRole::Click => "a click",
HandleRole::Tint => "a tint",
HandleRole::Bar => "a bar's fill",
}
}
2 changes: 2 additions & 0 deletions crates/window-core/src/codegen/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ fn window_imports(manifest: &Manifest) -> Vec<&'static str> {
(any(&|w| !w.collections.is_empty()), "CollectionEntry"),
(any(&|w| !w.inputs.is_empty()), "AnvilInputEntry"),
(any(&|w| !w.art.is_empty()), "ArtEntry"),
(any(&|w| w.art.values().any(|art| art.bar.is_some())), "BarEntry"),
(has_hitbox, "HitboxEntry"),
(any(&|w| !w.items.is_empty()), "ItemEntry"),
(has_layers, "LayerEntry"),
Expand Down Expand Up @@ -149,6 +150,7 @@ pub(super) fn generate_hud_entries(manifest: &Manifest, package_name: &str) -> O
let imports = [
(true, "Align"),
(manifest.huds.values().any(|hud| !hud.art.is_empty()), "ArtEntry"),
(manifest.huds.values().any(|hud| hud.art.values().any(|art| art.bar.is_some())), "BarEntry"),
(true, "HudEntry"),
(true, "HudShaderEntry"),
(true, "HudSurfaceEntry"),
Expand Down
16 changes: 15 additions & 1 deletion crates/window-core/src/codegen/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::BTreeMap;

use crate::ir::{Hitbox, Layer, Tooltip};
use crate::manifest::{
AnvilInputEntry, ArtEntry, CollectionEntry, FontMetricsEntry, HudShaderEntry, HudSurfaceEntry, ItemEntry,
AnvilInputEntry, ArtEntry, BarEntry, CollectionEntry, FontMetricsEntry, HudShaderEntry, HudSurfaceEntry, ItemEntry,
RegionEntry, SlotAreaEntry, SlotEntry, SlotLinesEntry, SlotRefEntry, SpriteEntry, SpriteSlotEntry, SurfaceEntry,
SwitchCaseEntry, SwitchEntry, TextOverflow,
};
Expand Down Expand Up @@ -113,9 +113,23 @@ pub(super) fn sprite_slot_entry_expr(slot: &SpriteSlotEntry, level: usize) -> St

pub(super) fn art_entry_expr(art: &ArtEntry, level: usize) -> String {
let call = Call::new("ArtEntry", level).arg("static", kt_string(&art.static_text));
let call = match &art.bar {
Some(bar) => call.arg("bar", bar_entry_expr(bar, level + 1)),
None => call,
};
with_tint(call, art.tint.as_ref(), &art.tint_markers, level).finish()
}

fn bar_entry_expr(bar: &BarEntry, level: usize) -> String {
Call::new("BarEntry", level)
.arg("name", kt_string(&bar.name))
.arg("x", bar.x)
.arg("width", bar.width)
.arg("levels", string_list_expr(&bar.levels))
.arg("advances", char_map(&bar.advances, level + 1))
.finish()
}

/// A `mapOf` of strings.
pub(super) fn strings_map(values: &BTreeMap<String, String>, level: usize) -> String {
string_map(values, level, |value, _| kt_string(value))
Expand Down
6 changes: 6 additions & 0 deletions crates/window-core/src/codegen/view/handles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ impl HandleMember {
format!("The palette entry the `{id}` tint draws in."),
format!("{member}(): {enum_name}"),
),
HandleKind::Fraction => abstract_fun(
w,
format!("How much of the `{id}` bar is filled, from 0 to 1."),
format!("{member}({params}): Float"),
),
HandleKind::Builtin => {}
}
}
Expand Down Expand Up @@ -331,6 +336,7 @@ fn bind_use(w: &mut KotlinWriter, handle: &HandleMember, use_: &HandleUse, args:
w.line(format_args!("switch({entry}) {{ {value} }}"));
}
HandleRole::Tint => w.line(format_args!("tint({entry}) {{ {member}().value }}")),
HandleRole::Bar => w.line(format_args!("bar({entry}) {{ {member}({args}) }}")),
// A runtime action cannot be bound; the runtime runs it.
HandleRole::Click if handle.handle.kind == HandleKind::Builtin => {}
HandleRole::Click => Handler::of(handle, use_, args).call(w, &format!("button({entry}")),
Expand Down
7 changes: 6 additions & 1 deletion crates/window-core/src/debug/surfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,12 @@ fn tree_layers(
Layer::Art(name) => {
let art = entries.art.get(name);
let layer = art.and_then(|art| static_layer(manifest, &art.static_text, origin, glyphs, Some(0)));
if let Some(layer) = layer {
if let Some(mut layer) = layer {
// A bar's first glyph is one clip of its fill; the layer covers the whole fill.
if let Some(bar) = art.and_then(|art| art.bar.as_ref()) {
layer.bounds[0] = origin[0] + bar.x;
layer.bounds[2] = bar.width as i32;
}
out.push(DebugLayer { id: format!("art:{name}"), kind: "art_chrome".into(), ..layer });
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/window-core/src/hud/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ fn segment_markers_are_green_zero_unique_and_stable() {
fn tinted_segments_get_a_marker_per_palette_entry_drawn_in_its_color() {
let mut hud = shader_hud("status", "coins");
hud.slots[0].tint = Some("tone".into());
hud.art.push(ArtIr { name: "art/0".into(), draws: Vec::new(), tint: Some("tone".into()) });
hud.art.push(ArtIr { name: "art/0".into(), draws: Vec::new(), tint: Some("tone".into()), bar: None });
let tone = Handle {
kind: HandleKind::Tint,
values: vec!["ok".into(), "warn".into()],
Expand Down
28 changes: 25 additions & 3 deletions crates/window-core/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ pub enum HandleKind {
Builtin,
/// One of `values`, computed by Kotlin, naming the palette color tinted art and text take.
Tint,
/// A number from 0 to 1, computed by Kotlin: how much of a bar is filled.
Fraction,
}

impl HandleKind {
Expand All @@ -274,6 +276,7 @@ impl HandleKind {
Self::Input => "input",
Self::Builtin => "builtin",
Self::Tint => "tint",
Self::Fraction => "fraction",
}
}
}
Expand All @@ -298,6 +301,8 @@ pub enum HandleRole {
Click,
/// Colors tinted art and text.
Tint,
/// Fills a bar.
Bar,
}

/// One element's use of a handle, bound to the manifest entry `entry`.
Expand Down Expand Up @@ -460,8 +465,8 @@ pub struct AnvilInputIr {
pub item_model: Option<String>,
}

/// Art the runtime draws as its own glyph, at its place among the runtime layers: tinted art, and the untinted art
/// authored over it.
/// Art the runtime draws as its own glyphs, at its place among the runtime layers: tinted art, the untinted art
/// authored over it, and the fill of a bar.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtIr {
/// Unique name within the surface, `art/N` in document order.
Expand All @@ -470,6 +475,15 @@ pub struct ArtIr {
pub draws: Vec<Draw>,
/// The tint handle whose current palette color the glyph is multiplied by; `None` draws it as authored.
pub tint: Option<String>,
/// The entry whose fraction decides how many of the art's columns are drawn, counted from its left edge;
/// `None` draws them all.
pub bar: Option<String>,
}

/// The columns a bar of `width` columns draws at `fraction`: the nearest whole column, with `fraction` clamped to
/// 0 through 1. Computed in single precision, as the runtime does.
pub fn bar_columns(fraction: f64, width: u32) -> u32 {
((fraction as f32).clamp(0.0, 1.0) * width as f32).round() as u32
}

/// A runtime-selected group of cases, of which at most one is active.
Expand Down Expand Up @@ -518,7 +532,7 @@ pub enum Layer {
Switch(String),
/// A collection's selected-cell sprite.
Collection(String),
/// An art layer: tinted art, or untinted art authored over it.
/// An art layer: tinted art, untinted art authored over it, or the fill of a bar.
Art(String),
}

Expand Down Expand Up @@ -590,6 +604,14 @@ pub struct LaidOutHud {
mod tests {
use super::*;

#[test]
fn bar_columns_round_in_single_precision() {
// 0.29 * 50 is 14.499… in double precision and 14.5 in single.
assert_eq!(bar_columns(0.29, 50), 15);
assert_eq!(bar_columns(1.5, 256), 256);
assert_eq!(bar_columns(-1.0, 256), 0);
}

#[test]
fn rgb_hex_round_trip() {
let c = Rgb::parse_hex("#FFd700").unwrap();
Expand Down
2 changes: 2 additions & 0 deletions crates/window-core/src/layout/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ impl<T: LayoutTarget> Solver<'_, T> {
| Element::Slot { .. }
| Element::Sprite { .. }
| Element::SpriteSlot { .. }
| Element::Bar(_)
| Element::Region(_) => self.measure(el),
other => Err(self.target.layout_err(format!(
"{} cannot be laid out inside a flex box; give it a slot source or place it in a section",
Expand Down Expand Up @@ -685,6 +686,7 @@ fn describe(el: &Element) -> String {
Element::Slot { name, .. } => format!("slot `{name}`"),
Element::SpriteSlot { name, .. } => format!("sprite slot `{name}`"),
Element::Sprite { name, .. } => format!("image `{name}`"),
Element::Bar(bar) => format!("bar `{}`", bar.name),
Element::Label { text, .. } => format!("label `{text}`"),
Element::Section(section) => format!("{} section", section_name(section)),
Element::Flex(_) => "flex box".into(),
Expand Down
1 change: 1 addition & 0 deletions crates/window-core/src/layout/measure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl<'a, T: LayoutTarget> Solver<'a, T> {
self.require_sprite_slots(name)?;
Ok(*size)
}
Element::Bar(bar) => Ok(bar.size),
Element::Label { text, width, style, .. } => {
let w = match width {
Some(width) => *width,
Expand Down
3 changes: 3 additions & 0 deletions crates/window-core/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ fn pos_of(el: &Element) -> Option<Point> {
| Element::SpriteSlot { pos, .. }
| Element::Label { pos, .. }
| Element::Slot { pos, .. } => *pos,
Element::Bar(bar) => bar.pos,
Element::Flex(node) => node.pos,
Element::Switch(switch) => switch.pos,
Element::Item { .. }
Expand All @@ -186,6 +187,7 @@ fn debug_name_of(el: &Element) -> Option<&str> {
| Element::AnvilInput { debug_name, .. }
| Element::Label { debug_name, .. }
| Element::Slot { debug_name, .. } => debug_name.as_deref(),
Element::Bar(bar) => bar.debug_name.as_deref(),
Element::Region(region) => region.debug_name.as_deref(),
Element::Flex(node) => node.debug_name.as_deref(),
Element::Section(section) => section.debug_name.as_deref(),
Expand Down Expand Up @@ -371,6 +373,7 @@ impl<'a, T: LayoutTarget> Solver<'a, T> {
Element::SpriteSlot { name, size, align, tint, .. } => {
self.place_sprite_slot(name, Rect::from_parts(origin, *size), *align, tint.clone())
}
Element::Bar(bar) => self.place_bar(bar, Rect::from_parts(origin, bar.size)),
Element::Label { text, width, style, .. } => self.place_label(text, *width, style, origin),
Element::Slot { name, width, style, fit, .. } => self.place_slot(name, *width, style, *fit, origin),
Element::Flex(node) => self.place_flex(node, origin, None),
Expand Down
Loading
Loading