Shelve GUI system, set up new Rust project structure

This commit is contained in:
Keavon Chambers 2021-02-14 17:57:17 -08:00
parent 55850ca9a7
commit bca97cbeff
43 changed files with 51 additions and 4888 deletions

2267
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,26 +1,7 @@
[package]
name = "graphite"
version = "0.1.0"
authors = ["Keavon Chambers <graphite@keavon.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.22"
winit = "0.20"
wgpu = "0.5"
glsl-to-spirv = "0.1"
failure = "0.1.7"
cgmath = "0.17"
palette = "0.5"
futures = "0.3.4"
bytemuck = "1.2.0"
rctree = "0.3.3"
xmlparser = "0.13.1"
regex = "1.3.7"
css-color-parser = "0.1.2"
env_logger = { version = "0.7.1", optional = true }
[workspace]
[features]
debug = ["env_logger"]
members = [
"packages/*",
]

View file

@ -1,12 +0,0 @@
<window:main>
<col width="100%" height="100%">
</col>
</window:main>
Start with the given root component
For each child_component:
Get the component of the associated child_component tag
Create a DomNode for this component
Give this DomNode its width and height and other layout attributes
Recursively load its children

View file

@ -1,260 +0,0 @@
# GUI System Explainer
This directory contains the XML files describing the components which make up Graphite's GUI.
## Principles
The framework is inspired by [Vue.js](https://vuejs.org/).
Each component's layout is defined in an XML, and recursively made out of lower-level components.
Interactivity is provided by script files which expose reactive variables. As these variables are mutated, the component is updated to match the current state.
## Layout
The layout engine does a top-down pass through the component tree in order to determine what to render.
Layout is controlled using predefined attributes, such as `width`, `height`, `x-align`, `y-align`, `gap` or `padding`.
## Component lifetime
The children of a component are passed to it as a `children` attribute. For example, looking at the row component:
```xml
<row children="INNER_XML: (Layout) = [[]]">
{{INNER_XML}}
</row>
```
The `children` attribute defines a new variable `INNER_XML` of type `Layout` which can contain more XML layout structure. It has a default value of `[[]]` which refers to an empty layout— XML syntax (for the `Layout` data type) written in a tag's attribute is wrapped in ``[[`` (opening) and `]]` (closing) symbols. In this case the `INNER_XML` variable defaults to empty XML, however it is not strictly useful here because the `children` attribute will always have its value replaced by whatever exists between opening and closing tags when this component is called from elsewhere.
This is then expanded in the body of the row: `{{INNER_XML}}`.
## Defining new components
### Component files
To define a new component, create a new `.xml` file in this directory. Subdirectories become namespaces for the components (e.g. the file `window/main.xml` defines a component `<window:main>`).
### Parameters
User-defined parameters start with a colon (`:`).
They are created by adding attributes to a component source file:
`:parameter="VARIABLE_NAME: (VariableType) = defaultValue"`
# GUI System Markup Language Specification
## Layouts
* XML files laying out interface structure with tags for layouts and primitives
* Namespaced with folder name like `<namespace:layout-name>`
* Inner XML content bound to the variable specified in the `children` attribute of the root element definition
* All custom attributes are prefixed with a `:` when used as arguments and as parameters
* Root element in each file is the layout and its accepted arguments with a bound variable and default value
* Templating using {{mustaches}} for bound variables and computed values in the associated script
* Each layout has a companion script (Rust or WASM) that exposes computed values for templating
* Each layout acts as a container element used in computing layout measurements
## GUI layout tree data structure
* Stores purely the data used by the renderer and shaders
* Updated by the layout system
## Primitive layouts
**`<box> | <box />`** Draws a box
* **`children`** *`[xml | none = none]`*
Inner XML stays in the document
* **`:fill`** *`[color | none = none]`*
Fill color for the box
* **`:round`** *`[size | size size size size = 0px]`*
Rounds the corners
* **`:border-thickness`** *`[size = 0px]`*
Thickness of the border inside the box
* **`:border-color`** *`[color | none = none]`*
Color of the border inside the box
**`<icon> | <icon />`** Draws an icon from an SVG file and optionally contains child elements
* **`children`** *`[xml | none = none]`*
Inner XML stays in the document
* **`:svg`** *``[string = `missing_svg_alert.svg`]``*
Location of the SVG file
* **`:style`** *```[string = ``]```*
CSS styling to be applied to the SVG, useful for applying templated variables
**`<text>`** Draws text
* **`children`** *```[string = ``]```*
The text to be drawn (eventually this could become XML for styling)
* **`:color`** *`[color | none = [middlegray]]`*
The color of the text
* **`:size`** *`[size = 12px]`*
The size of the text
**`<row> | <row />`** Wraps content laid out across vertically-adjacent sections, or acts as a spacer
* **`children`** *`[xml | none = none]`*
The elements inside the row
**`<col>`** Wraps content laid out across horizontally-adjacent sections, or acts as a spacer
* **`children`** *`[xml | none = none]`*
The elements inside the column
**`<if>`** Conditionally enables or disables child content if :a equals :b
* **`children`** *`[xml | none = none]`*
The elements to be shown if :a equals :b
* **`:a`** *`[TypeValue = true]`*
The first variable that must equal the second variable
* **`:b`** *`[TypeValue = true]`*
The second variable that must equal the first variable
## Layout calculation
**`width`** *`[Dimension = inner]`*
Set the exact content width of the element
**`height`** *`[Dimension = inner]`*
Set the exact content height of the element
**`x-align`** *`[Dimension::Percent = 0%]`*
Factor from left (0%) to right (100%) to align content inside this larger element
**`y-align`** *`[Dimension::Percent = 0%]`*
Factor from top (0%) to bottom (100%) to align content inside this larger element
**`gap`** *`[Dimension Dimension Dimension Dimension = 0px 0px 0px 0px]`*
Collapses between neighbors, pushes/expands parent set to inner, not part of click target (negative values count against the interior dimension instead of adding to the outside of the dimension?)
* **gap** *[Dimension → a a a a]*
Sugar: Single value for all sides
* **gap** *[Dimension Dimension = a b a b]*
Sugar: Two values for top/bottom and left/right
* **x-gap** *[Dimension = 0px a 0px a]*
Sugar: Single value for left/right
* **x-gap** *[Dimension Dimension = 0px a 0px b]*
Sugar: Two values for left and right
* **y-gap** *[Dimension = a 0px a 0px]*
Sugar: Single value for top/bottom
* **y-gap** *[Dimension Dimension = a 0px b 0px]*
Sugar: Two values for top and bottom
**`padding`** *`[Dimension Dimension Dimension Dimension = 0]`*
Doesnt collapse between neighbors, pushes/expands parent set to inner, part of the click target (negative values count against the interior dimension instead of adding to the outside of the dimension?)
* **padding** *[Dimension → a a a a]*
Sugar: Single value for all sides
* **padding** *[Dimension Dimension = a b a b]*
Sugar: Two values for top/bottom and left/right
* **x-padding** *[Dimension = 0px a 0px a]*
Sugar: Single value for left/right
* **x-padding** *[Dimension Dimension = 0px a 0px b]*
Sugar: Two values for left and right
* **y-padding** *[Dimension = a 0px a 0px]*
Sugar: Single value for top/bottom
* **y-padding** *[Dimension Dimension = a 0px b 0px]*
Sugar: Two values for top and bottom
**`scroll`** *`[Dimension::Percent = 0%]`*
When child elements overflow their container, keep them visible on the top/left (0%) or bottom/right (100%) while clipping on the opposite side
## Variables
Parameter
* Attribute: **?: (T1 | … | Tn) = ?**
Declares a parameter with a list of possible types and a required default value
* ```^\s*({{)\s*(\w*)\s*(:)\s*(\()\s*(\w*\s*(?:\|\s*\w*\s*?)*)\s*(\))\s*(=)\s*(\w*)\s*(}})\s*$```
* ```{{ THE_NAME : (bool | color | inner | percent ) = none }}```
* ```Value Type: (String, Vec<TypeName>, TypeValue)```
Argument
* Attribute: {{?}}
In an attribute, string, or between tags, evaluates to another type value via environment lookup
* ```^\s*({{)\s*(\w*)\s*(}})\s*$```
* ```{{THE_NAME }}```
* ```Value Type: String```
## Types
**`Layout`**
* Attribute: **`[[<? ...>...</?>]]`**
The XML layout language wrapped in double square brackets
Body: **`<? ...>...</?>`**
XML data
* Value Type: Abstract syntax tree?
**`AbsolutePx`**
* Attribute: **`?px`**
Absolute size in UI pixels
* `^\s*(-?\d*(?:\.\d*)?)([Pp][Xx])\s*$`
* Value Type: `f32`
**`Percent`**
* Attribute: **`?%`**
Percentage of the total size of the parent container
* `^\s*(-?\d*(?:\.\d*)?)(%)\s*$`
* Value Type: `f32`
**`PercentRemainder`**
* Attribute: **`?@`**
Percentage of the remainder of unfilled space within the parent container
* `^\s*(-?\d*(?:\.\d*)?)(@)\s*$`
* Value Type: `f32`
**`Inner`**
* Attribute: **`inner`**
Use the width/height of the content, where any child percent-based values become inner
* `^\s*([Ii][Nn][Nn][Ee][Rr])\s*$`
* Value Type: N/A
**`Width`**
* Attribute: **`width`**
Copies the computed width from the current element
* `^\s*([Ww][Ii][Dd][Tt][Hh])\s*$`
* Value Type: N/A
**`Height`**
* Attribute: **`height`**
Copies the computed height from the current element
* `^\s*([Hh][Ee][Ii][Gg][Hh][Tt])\s*$`
* Value Type: N/A
**`TemplateString`**
* Attribute: **`` `? … {{?}} …` ``**
A string with arguments inside, wrapped in backticks
Body: **`? {{?}} ? … ? {{?}}`**
Not to be mixed with other sibling XML tags
* ``^\s*`(.*)`\s*$``
* Value Type: `Vec<String | Argument>`
**`Color`**
* Attribute: **`['?']`**
Literal name read from the standard color palette
Attribute: **`[?]`**
CSS color parsed by [rust-css-color](https://github.com/kalcutter/rust-css-color)
* `^\s*(\[)(.*)(\])\s*$`
* Value Type: `Color`
**`Bool`**
* Attribute: **`true`**
The true value
Attribute: **`false`**
The false value
* `^\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\s*$`
* Value Type: `bool`
**`None`**
* Attribute: **`none`**
Indicates the absence of a value
* `^\s*([Nn][Oo][Nn][Ee])\s*$`
* Value Type: N/A
## Drawing procedure
Depth or breadth first traversal, shallow nodes drawn before deeper nodes.
## Updating and damaged flag
For any element marked damaged, it and all its children are redrawn.
Resizing panels marks all affected panel containers as damaged so the resized contents are drawn.
## Antialiased corners
Pass along the parent nodes uniform, for any fragment located within a corner region, render the parent and blend antialiased GUI rectangle over it based on signed-distance function corner.
Pixels in the corner regions must be wholly from the parent (must live within the borders of the parent, parent cant be transparent, parent cant have a wider overlapping border radius).
Requires a special case for overlapping parent and child with same border radius in same location so only the child is shown to avoid bleeding doubled antialiased edges.

View file

@ -1,3 +0,0 @@
<box children="INNER_XML: (Layout) = [[]]" :fill="FILL: (Color) = ['middlegray']" :round="ROUND: (AbsolutePx | AbsolutePx, AbsolutePx, AbsolutePx, AbsolutePx) = 0px" :border-thickness="BORDER_THICKNESS: (AbsolutePx) = 0px" :border-color="BORDER_COLOR: (Color) = ['black']">
{{INNER_XML}}
</box>

View file

@ -1,3 +0,0 @@
<col children="INNER_XML: (Layout) = [[]]">
{{INNER_XML}}
</col>

View file

@ -1,20 +0,0 @@
<header:file-menu>
<box height="100%" x-padding="10px">
<text height="100%" y-align="50%" :color="['mildwhite']"></text>
</box>
<box height="100%" x-padding="10px">
<text height="100%" y-align="50%" :color="['mildwhite']">File</text>
</box>
<box height="100%" x-padding="10px">
<text height="100%" y-align="50%" :color="['mildwhite']">Edit</text>
</box>
<box height="100%" x-padding="10px">
<text height="100%" y-align="50%" :color="['mildwhite']">Comp</text>
</box>
<box height="100%" x-padding="10px">
<text height="100%" y-align="50%" :color="['mildwhite']">View</text>
</box>
<box height="100%" x-padding="10px">
<text height="100%" y-align="50%" :color="['mildwhite']">Help</text>
</box>
</header:file-menu>

View file

@ -1,16 +0,0 @@
<header:window-buttons :maximized="IS_MAXIMIZED: (Bool) = false">
<box height="100%" y-align="50%" x-padding="18px">
<icon :svg="`window_minimize.svg`" />
</box>
<box height="100%" y-align="50%" x-padding="18px">
<if :a="{{IS_MAXIMIZED}}">
<icon :svg="`window_restore_down.svg`" />
</if>
<if :a="{{IS_MAXIMIZED}}" :b="false">
<icon :svg="`maximize.svg`" />
</if>
</box>
<box height="100%" y-align="50%" x-padding="18px">
<icon :svg="`window_close.svg`" />
</box>
</header:window-buttons>

View file

@ -1,3 +0,0 @@
<icon children="INNER_XML: (Layout) = [[]]" :svg="SVG_SOURCE: (TemplateString) = ``" :style="SVG_STYLE: (TemplateString) = ``">
{{INNER_XML}}
</icon>

View file

@ -1,3 +0,0 @@
<if children="INNER_XML: (Layout) = [[]]" :a="CONDITION_A: (Integer | Decimal | AbsolutePx | Percent | PercentRemainder | Inner | Width | Height | TemplateString | Color | Bool | None) = true" :b="CONDITION_B: (Integer | Decimal | AbsolutePx | Percent | PercentRemainder | Inner | Width | Height | TemplateString | Color | Bool | None) = true">
{{RESULT}}
</if>

View file

@ -1,12 +0,0 @@
<input:checkbox-with-dropdown children="OPTION_LIST: (Layout) = [[]]" :disabled="DISABLED: (Bool) = false" :checked="CHECKED: (Bool) = false" :selected-index="SELECTED_INDEX: (Integer) = 0">
<!-- Checkbox -->
<col width="height" height="100%">
<box width="100%" height="100%" x-align="50%" y-align="50%" :round="4px, 0px, 0px, 4px" :fill="['accent']">
<input:checkbox :checked="{{CHECKED}}" :disabled="{{DISABLED}}" :inverted="true" />
</box>
</col>
<!-- Dropdown -->
<col width="100%">
<input:dropdown width="100%" :selected-index="{{SELECTED_INDEX}}" :disabled="{{DISABLED}}">{{OPTION_LIST}}</input:dropdown>
</col>
</input:checkbox-with-dropdown>

View file

@ -1,12 +0,0 @@
<input:checkbox :checked="CHECKED: (Bool) = false" :disabled="DISABLED: (Bool) = false" :inverted="INVERTED: (Bool) = false">
<box width="14px" height="14px" :fill="{{BOX_COLOR}}" :round="2px">
<if :a="{{CHECKED}}">
<if :a="{{INVERTED}}" :b="false">
<icon :svg="`checkmark_white.svg`" />
</if>
<if :a="{{INVERTED}}">
<icon :svg="`checkmark_accent.svg`" />
</if>
</if>
</box>
</input:checkbox>

View file

@ -1,12 +0,0 @@
<input:dropdown children="OPTION_LIST: (TemplateString) = ``" :selected-index="SELECTED_INDEX: (Integer) = 0" :disabled="DISABLED: (Bool) = false">
<box width="100%" :round="4px">
<!-- Current selection text -->
<col width="100%" height="24px">
<text width="100%" height="100%" x-align="50%" y-align="50%" :color="['mildwhite']">{{CURRENT_TEXT}}</text>
</col>
<!-- Dropdown arrow icon -->
<col width="8px" height="100%">
<icon width="8px" height="100%" x-align="50%" y-align="50%" :svg="`dropdown_arrow.svg`" />
</col>
</box>
</input:dropdown>

View file

@ -1,3 +0,0 @@
<row children="INNER_XML: (Layout) = [[]]">
{{INNER_XML}}
</row>

View file

@ -1 +0,0 @@
<text children="TEXT_STRING: (TemplateString) = `MISSING TEXT CONTENT`" :color="COLOR: (Color) = ['middlegray']" :size="SIZE: (AbsolutePx) = 12px"></text>

View file

@ -1,7 +0,0 @@
<viewport:panels>
<input:checkbox-with-dropdown :checked="true" :selected-index="2">
Option A
Option B
Option C
</input:checkbox-with-dropdown>
</viewport:panels>

View file

@ -1,20 +0,0 @@
<!-- Instantiated by the window and called with absolute dimensions such as: <window:main width="1920px" height="1080px" /> -->
<window:main>
<col width="100%" height="100%">
<!-- Header -->
<row width="100%" height="28px">
<header:file-menu height="100%" x-align="0%" />
<text height="100%" y-align="50%" x-align="50%" :color="['mildwhite']">Document 1* - Graphite</text>
<header:window-buttons height="100%" x-align="100%" />
</row>
<!-- Viewport -->
<row width="100%" height="100@">
<viewport:panels width="100%" height="100%" />
</row>
<!-- Footer -->
<row width="100%" height="24px">
<text height="100%" y-align="50%" x-align="0%" x-padding="10px" :color="['mildwhite']" :size="14px">File: 1.8 MB | Memory: 137 MB | Scratch: 0.7/12.3 GB</text>
<text height="100%" y-align="50%" x-align="100%" x-padding="10px" :color="['mildwhite']" :size="14px">Box Select Objects | [⇧G] Move Selection | [⇧R] Rotate Selection | [⇧S] Scale Selection</text>
</row>
</col>
</window:main>

9
packages/cli/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "cli"
version = "0.1.0"
authors = ["Keavon Chambers <keavon@keavon.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

3
packages/cli/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, Graphite!");
}

View file

@ -0,0 +1,9 @@
[package]
name = "render-engine"
version = "0.1.0"
authors = ["Keavon Chambers <keavon@keavon.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View file

@ -0,0 +1,9 @@
[package]
name = "wasm-bindings"
version = "0.1.0"
authors = ["Keavon Chambers <keavon@keavon.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View file

@ -1,25 +0,0 @@
#version 450
layout(location=0) in vec2 v_uv;
layout(location=0) out vec4 f_color;
struct Dimensions_u32 { uint width; uint height; };
struct Corners_f32 { float top_left; float top_right; float bottom_right; float bottom_left; };
struct Sides_f32 { float top; float right; float bottom; float left; };
layout(set=0, binding=0) uniform GuiNodeUniform {
Dimensions_u32 dimensions;
Corners_f32 corners_radius;
Sides_f32 sides_inset;
float border_thickness;
vec4 border_color;
vec4 fill_color;
};
layout(set=0, binding=1) uniform texture2D t_texture;
layout(set=0, binding=2) uniform sampler s_texture;
void main() {
f_color = fill_color * texture(sampler2D(t_texture, s_texture), v_uv / textureSize(sampler2D(t_texture, s_texture), 0) * 500);
}

View file

@ -1,10 +0,0 @@
#version 450
layout(location=0) in vec2 a_position;
layout(location=0) out vec2 v_uv;
void main() {
v_uv = (a_position + 1) / 2;
gl_Position = vec4(a_position, 0.0, 1.0);
}

View file

@ -1,194 +0,0 @@
use crate::color_palette::ColorPalette;
use crate::gui_node::GuiNode;
use crate::layout_system::LayoutSystem;
use crate::pipeline::Pipeline;
use crate::resource_cache::ResourceCache;
use crate::texture::Texture;
use crate::window_events;
use futures::executor::block_on;
use winit::event::*;
use winit::event_loop::*;
use winit::window::Window;
pub struct Application {
pub surface: wgpu::Surface,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub swap_chain_descriptor: wgpu::SwapChainDescriptor,
pub swap_chain: wgpu::SwapChain,
pub shader_cache: ResourceCache<wgpu::ShaderModule>,
pub pipeline_cache: ResourceCache<Pipeline>,
pub texture_cache: ResourceCache<Texture>,
pub gui_root: rctree::Node<GuiNode>,
}
impl Application {
pub fn new(window: &Window) -> Self {
// Window as understood by WGPU for rendering onto
let surface = wgpu::Surface::create(window);
// Represents a GPU, exposes the real GPU device and queue
let adapter = block_on(wgpu::Adapter::request(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::Default,
compatible_surface: Some(&surface),
},
wgpu::BackendBit::PRIMARY,
))
.unwrap();
// Requests the device and queue from the adapter
let requested_device = block_on(adapter.request_device(&wgpu::DeviceDescriptor {
extensions: wgpu::Extensions { anisotropic_filtering: false },
limits: Default::default(),
}));
// Connection to the physical GPU
let device = requested_device.0;
// Represents the GPU command queue, to submit CommandBuffers
let queue = requested_device.1;
// Properties for the swap chain frame buffers
let swap_chain_descriptor = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
width: window.inner_size().width,
height: window.inner_size().height,
present_mode: wgpu::PresentMode::Fifo,
};
// Series of frame buffers with images presented to the surface
let swap_chain = device.create_swap_chain(&surface, &swap_chain_descriptor);
// Resource caches that own the application's shaders, pipelines, and textures
let mut shader_cache = ResourceCache::<wgpu::ShaderModule>::new();
let mut pipeline_cache = ResourceCache::<Pipeline>::new();
let texture_cache = ResourceCache::<Texture>::new();
// Temporary setup below, TODO: move to appropriate place in architecture
// Data structure maintaining the user interface
let gui_rect_pipeline = Pipeline::new(
&device,
swap_chain_descriptor.format,
Vec::new(),
&mut shader_cache,
("shaders/shader.vert", "shaders/shader.frag"),
);
pipeline_cache.set("gui_rect", gui_rect_pipeline);
// Render quad hierarchy
let gui_root_data = GuiNode::new(swap_chain_descriptor.width, swap_chain_descriptor.height, ColorPalette::Accent.into_color_srgb());
let gui_root = rctree::Node::new(gui_root_data);
// Main window in the XML layout language
let mut main_window_layout = LayoutSystem::new();
main_window_layout.add_window(("window", "main"), (1920, 1080));
Self {
surface,
adapter,
device,
queue,
swap_chain_descriptor,
swap_chain,
shader_cache,
pipeline_cache,
texture_cache,
gui_root,
}
}
// Initializes the event loop for rendering and event handling
pub fn begin_lifecycle(mut self, event_loop: EventLoop<()>, window: Window) {
event_loop.run(move |event, _, control_flow| self.main_event_loop(event, control_flow, &window));
}
// Called every time by the event loop
fn main_event_loop<T>(&mut self, event: Event<'_, T>, control_flow: &mut ControlFlow, window: &Window) {
// Wait for the next event to cause a subsequent event loop run, instead of looping instantly as a game would need
*control_flow = ControlFlow::Wait;
match event {
// Handle all window events (like input and resize) in sequence
Event::WindowEvent { window_id, ref event } if window_id == window.id() => window_events::window_event(self, control_flow, event),
// Handle raw hardware-related events not related to a window
Event::DeviceEvent { .. } => (),
// Handle custom-dispatched events
Event::UserEvent(_) => (),
// Called once every event is handled and the GUI structure is updated
Event::MainEventsCleared => self.update_gui(),
// Resizing or calling `window.request_redraw()` renders the GUI with the queued draw commands
Event::RedrawRequested(_) => self.render(),
// Once all windows have been redrawn
Event::RedrawEventsCleared => (),
Event::NewEvents(_) => (),
Event::Suspended => (),
Event::Resumed => (),
Event::LoopDestroyed => (),
_ => (),
}
}
fn update_gui(&mut self) {}
// Render the queue of pipeline draw commands over the current window
fn render(&mut self) {
// Get a frame buffer to render on
let frame = self.swap_chain.get_next_texture().expect("Timeout getting frame buffer texture");
// Generates a render pass that commands are applied to, then generates a command buffer when finished
let mut command_encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
// Build an array of draw commands by traversing the GUI element tree
let commands = GuiNode::build_draw_commands_recursive(&self.gui_root, &self.device, &mut self.queue, &self.pipeline_cache, &mut self.texture_cache);
// Recording of commands while in "rendering mode" that go into a command buffer
let mut render_pass = command_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: &frame.view,
resolve_target: None,
load_op: wgpu::LoadOp::Clear,
store_op: wgpu::StoreOp::Store,
clear_color: wgpu::Color::BLACK,
}],
depth_stencil_attachment: None,
});
// Prepare a variable to reuse the pipeline based on its name
let mut pipeline_name = String::new();
// Turn the queue of pipelines each into a command buffer and submit it to the render queue
for i in 0..commands.len() {
// If the previously set pipeline can't be reused, send the GPU the new pipeline to draw with
if pipeline_name != commands[i].pipeline_name {
let pipeline = self.pipeline_cache.get(&commands[i].pipeline_name[..]).unwrap();
render_pass.set_pipeline(&pipeline.render_pipeline);
pipeline_name = commands[i].pipeline_name.clone();
}
// Send the GPU the vertices and triangle indices
render_pass.set_vertex_buffer(0, &commands[i].vertex_buffer, 0, 0);
render_pass.set_index_buffer(&commands[i].index_buffer, 0, 0);
// Send the GPU the bind group resources
for (index, bind_group) in commands[i].bind_groups.iter().enumerate() {
render_pass.set_bind_group(index as u32, bind_group, &[]);
}
// Draw call
render_pass.draw_indexed(0..commands[i].index_count, 0, 0..1);
}
// Done sending render pass commands so we can give up mutation rights to command_encoder
drop(render_pass);
// Turn the recording of commands into a complete command buffer
let command_buffer = command_encoder.finish();
// Submit the command buffer to the GPU command queue
self.queue.submit(&[command_buffer]);
}
}

View file

@ -1,41 +0,0 @@
#[repr(C, align(16))]
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { r, g, b, a }
}
#[allow(dead_code)]
pub const TRANSPARENT: Self = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
#[allow(dead_code)]
pub const BLACK: Self = Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
#[allow(dead_code)]
pub const WHITE: Self = Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
#[allow(dead_code)]
pub const RED: Self = Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
#[allow(dead_code)]
pub const YELLOW: Self = Color { r: 1.0, g: 1.0, b: 0.0, a: 1.0 };
#[allow(dead_code)]
pub const GREEN: Self = Color { r: 0.0, g: 1.0, b: 0.0, a: 1.0 };
#[allow(dead_code)]
pub const CYAN: Self = Color { r: 0.0, g: 1.0, b: 1.0, a: 1.0 };
#[allow(dead_code)]
pub const BLUE: Self = Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
#[allow(dead_code)]
pub const MAGENTA: Self = Color { r: 1.0, g: 0.0, b: 1.0, a: 1.0 };
}

View file

@ -1,91 +0,0 @@
use crate::color::Color;
#[allow(dead_code)]
pub enum ColorPalette {
Black,
NearBlack,
MildBlack,
DarkGray,
DimGray,
DullGray,
LowerGray,
MiddleGray,
UpperGray,
PaleGray,
SoftGray,
LightGray,
BrightGray,
MildWhite,
NearWhite,
White,
Accent,
}
impl ColorPalette {
#[allow(dead_code)]
pub fn into_color_srgb(&self) -> Color {
let grayscale = match self {
ColorPalette::Black => 0 * 17, // #000000
ColorPalette::NearBlack => 1 * 17, // #111111
ColorPalette::MildBlack => 2 * 17, // #222222
ColorPalette::DarkGray => 3 * 17, // #333333
ColorPalette::DimGray => 4 * 17, // #444444
ColorPalette::DullGray => 5 * 17, // #555555
ColorPalette::LowerGray => 6 * 17, // #666666
ColorPalette::MiddleGray => 7 * 17, // #777777
ColorPalette::UpperGray => 8 * 17, // #888888
ColorPalette::PaleGray => 9 * 17, // #999999
ColorPalette::SoftGray => 10 * 17, // #aaaaaa
ColorPalette::LightGray => 11 * 17, // #bbbbbb
ColorPalette::BrightGray => 12 * 17, // #cccccc
ColorPalette::MildWhite => 13 * 17, // #dddddd
ColorPalette::NearWhite => 14 * 17, // #eeeeee
ColorPalette::White => 15 * 17, // #ffffff
_ => -1,
};
if grayscale > -1 {
let value = grayscale as f32 / 255.0;
return Color::new(value, value, value, 1.0);
}
let rgba = match self {
ColorPalette::Accent => (75, 121, 167, 255), // #4b79a7
_ => (0, 0, 0, 255), // Unimplemented returns black
};
Color::new(rgba.0 as f32 / 255.0, rgba.1 as f32 / 255.0, rgba.2 as f32 / 255.0, rgba.3 as f32 / 255.0)
}
#[allow(dead_code)]
pub fn into_color_linear(&self) -> Color {
let standard_rgb = ColorPalette::into_color_srgb(self);
let linear = palette::Srgb::new(standard_rgb.r, standard_rgb.g, standard_rgb.b).into_linear();
Color::new(linear.red, linear.green, linear.blue, standard_rgb.a)
}
pub fn lookup_palette_color(name_in_palette: &str) -> ColorPalette {
match &name_in_palette.to_ascii_lowercase()[..] {
"black" => ColorPalette::Black,
"nearblack" => ColorPalette::NearBlack,
"mildblack" => ColorPalette::MildBlack,
"darkgray" => ColorPalette::DarkGray,
"dimgray" => ColorPalette::DimGray,
"dullgray" => ColorPalette::DullGray,
"lowergray" => ColorPalette::LowerGray,
"middlegray" => ColorPalette::MiddleGray,
"uppergray" => ColorPalette::UpperGray,
"palegray" => ColorPalette::PaleGray,
"softgray" => ColorPalette::SoftGray,
"lightgray" => ColorPalette::LightGray,
"brightgray" => ColorPalette::BrightGray,
"mildwhite" => ColorPalette::MildWhite,
"nearwhite" => ColorPalette::NearWhite,
"white" => ColorPalette::White,
"accent" => ColorPalette::Accent,
_ => panic!("Invalid color lookup of `{}` from the color palette", name_in_palette),
}
}
}

View file

@ -1,25 +0,0 @@
// use crate::bind_group_resource::BindGroupResource;
pub struct DrawCommand {
pub pipeline_name: String,
pub bind_groups: Vec<wgpu::BindGroup>,
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub index_count: u32,
}
impl DrawCommand {
pub fn new(device: &wgpu::Device, pipeline_name: String, bind_groups: Vec<wgpu::BindGroup>, vertices: &[[f32; 2]], indices: &[u16]) -> Self {
let vertex_buffer = device.create_buffer_with_data(bytemuck::cast_slice(vertices), wgpu::BufferUsage::VERTEX);
let index_buffer = device.create_buffer_with_data(bytemuck::cast_slice(indices), wgpu::BufferUsage::INDEX);
let index_count = indices.len() as u32;
Self {
pipeline_name,
bind_groups,
vertex_buffer,
index_buffer,
index_count,
}
}
}

View file

@ -1,47 +0,0 @@
#[repr(C, align(16))]
#[derive(Debug, Copy, Clone)]
pub struct Corners<T> {
pub top_left: T,
pub top_right: T,
pub bottom_right: T,
pub bottom_left: T,
}
impl<T> Corners<T> {
pub fn new(top_left: T, top_right: T, bottom_right: T, bottom_left: T) -> Self {
Self {
top_left,
top_right,
bottom_right,
bottom_left,
}
}
}
#[repr(C, align(16))]
#[derive(Debug, Copy, Clone)]
pub struct Sides<T> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl<T> Sides<T> {
pub fn new(top: T, right: T, bottom: T, left: T) -> Self {
Self { top, right, bottom, left }
}
}
#[repr(C, align(16))]
#[derive(Debug, Copy, Clone)]
pub struct Dimensions<T> {
pub width: T,
pub height: T,
}
impl<T> Dimensions<T> {
pub fn new(width: T, height: T) -> Self {
Self { width, height }
}
}

View file

@ -1,97 +0,0 @@
use crate::color::Color;
use crate::draw_command::DrawCommand;
use crate::gui_attributes::*;
use crate::pipeline::Pipeline;
use crate::resource_cache::ResourceCache;
use crate::texture::Texture;
pub struct GuiNode {
pub form_factor: GuiNodeUniform,
pub pipeline_name: String,
}
impl GuiNode {
pub fn new(width: u32, height: u32, color: Color) -> Self {
Self {
form_factor: GuiNodeUniform::new(width, height, color),
pipeline_name: String::from("gui_rect"),
}
}
pub fn build_draw_commands_recursive(
node: &rctree::Node<GuiNode>,
device: &wgpu::Device,
queue: &mut wgpu::Queue,
pipeline_cache: &ResourceCache<Pipeline>,
texture_cache: &mut ResourceCache<Texture>,
) -> Vec<DrawCommand> {
let mut draw_commands: Vec<DrawCommand> = Vec::new();
for mut subnode in node.descendants() {
let mut subnode_data = subnode.borrow_mut();
let pipeline = pipeline_cache.get(&subnode_data.pipeline_name[..]).unwrap();
let command = subnode_data.build_draw_command(device, queue, pipeline, texture_cache);
draw_commands.push(command);
}
draw_commands
}
pub fn build_draw_command(&mut self, device: &wgpu::Device, queue: &mut wgpu::Queue, pipeline: &Pipeline, texture_cache: &mut ResourceCache<Texture>) -> DrawCommand {
const VERTICES: &[[f32; 2]] = &[[-0.5, 0.5], [0.5, 0.5], [0.5, 1.0], [-0.5, 1.0]];
const INDICES: &[u16] = &[0, 1, 2, 0, 2, 3];
let bind_groups = self.build_bind_groups(device, queue, pipeline, texture_cache);
// Create a draw command with the vertex data then push it to the GPU command queue
DrawCommand::new(device, self.pipeline_name.clone(), bind_groups, VERTICES, INDICES)
}
pub fn build_bind_groups(&mut self, device: &wgpu::Device, queue: &mut wgpu::Queue, pipeline: &Pipeline, texture_cache: &mut ResourceCache<Texture>) -> Vec<wgpu::BindGroup> {
// Load the cached texture
let texture = Texture::cached_load(device, queue, "textures/grid.png", texture_cache);
// Build a staging buffer from the uniform resource data
let binding_staging_buffer = Pipeline::build_binding_staging_buffer(device, &self.form_factor);
// Construct the bind group for this GUI node
let bind_group = Pipeline::build_bind_group(
device,
&pipeline.bind_group_layout,
vec![
Pipeline::build_binding_resource(&binding_staging_buffer),
wgpu::BindingResource::TextureView(&texture.texture_view),
wgpu::BindingResource::Sampler(&texture.sampler),
],
);
vec![bind_group]
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct GuiNodeUniform {
pub dimensions: Dimensions<u32>,
pub corners_radius: Corners<f32>,
pub sides_inset: Sides<f32>,
pub border_thickness: f32,
pub border_color: Color,
pub fill_color: Color,
}
impl GuiNodeUniform {
pub fn new(width: u32, height: u32, color: Color) -> Self {
GuiNodeUniform {
dimensions: Dimensions::<u32>::new(width, height),
corners_radius: Corners::<f32>::new(0.0, 0.0, 0.0, 0.0),
sides_inset: Sides::<f32>::new(0.0, 0.0, 0.0, 0.0),
border_thickness: 0.0,
border_color: Color::TRANSPARENT,
fill_color: color,
}
}
}
unsafe impl bytemuck::Zeroable for GuiNodeUniform {}
unsafe impl bytemuck::Pod for GuiNodeUniform {}

View file

@ -1,279 +0,0 @@
use crate::layout_abstract_types::*;
// AST for a component with info on its definition (from the root element of the XML layout) and a vector of direct child component tags
#[derive(Debug, Clone, PartialEq)]
pub struct FlatComponent {
// The abstract definition of the root node of the component with prop definitions
pub own_info: LayoutComponentDefinition,
// Only stores tags, text elements are disposed of (they'd be meaningless in a tag list)
pub child_components: Vec<LayoutComponentTag>,
}
/// A component in its final processed form (after parsing its XML file), with information on its definition with a list of child components with their own children in their `children` attributes
impl FlatComponent {
// Construct a layout component which stores its own root-level component definition (with prop definitions, etc.) and a flat list of its direct child tags, each with an AST in their `children` attribute
pub fn new(own_info: LayoutComponentDefinition, child_components: Vec<LayoutComponentTag>) -> FlatComponent {
Self { own_info, child_components }
}
/// Print the component (for debugging)
#[allow(dead_code)]
pub fn debug_print(&self) {
println!("Flat Component: {:#?}", self.own_info);
for tag in &self.child_components {
tag.debug_print();
}
}
}
// ====================================================================================================
/// Wrapper for either a `LayoutComponentNode` enum or `LayoutComponentDefinition` struct
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutComponentNodeOrDefinition {
LayoutComponentNode(LayoutComponentNode),
LayoutComponentDefinition(LayoutComponentDefinition),
}
// ====================================================================================================
/// AST of `LayoutComponentNode`s which hold either a tag or text node
pub type NodeTree = rctree::Node<LayoutComponentNode>;
/// AST similar to `NodeTree` (a tree of `LayoutComponentNode`s) but this holds the wrapped values `LayoutComponentNodeOrDefinition` (unwrap them with `LayoutSystem::node_tree_from_node_or_def_tree()`)
pub type NodeOrDefTree = rctree::Node<LayoutComponentNodeOrDefinition>;
// ====================================================================================================
/// Representation of an XML node with either another XML tag (`LayoutComponentTag`) or a text node (a vector of alternating `TemplateStringSegment::String`s and `TemplateStringSegment::Argument`s)
#[derive(Debug, Clone, PartialEq)]
pub enum LayoutComponentNode {
Tag(LayoutComponentTag),
Text(Vec<TemplateStringSegment>),
}
impl LayoutComponentNode {
/// Given a tag name in namespace:name format, construct a `LayoutComponentNode` that wraps a newly constructed `LayoutComponentTag` struct based on the provided name
pub fn new_tag(name: (String, String)) -> Self {
Self::Tag(LayoutComponentTag::new(name))
}
/// Given some text hanging out in the XML between tags, construct a `LayoutComponentNode` with that text which simply stores the provided `String`
pub fn new_text(text: Vec<TemplateStringSegment>) -> Self {
Self::Text(text)
}
/// Print the component node (for debugging)
#[allow(dead_code)]
pub fn debug_print(&self) {
match self {
LayoutComponentNode::Tag(tag) => tag.debug_print(),
LayoutComponentNode::Text(text) => println!("================> Text Node: {:#?}", text),
}
}
}
// ====================================================================================================
/// Abstract representation of a component based on the definitions of its props in the root tag of a component XML layout
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutComponentDefinition {
/// Name of the component in namespace:name format
pub name: (String, String),
/// Accepted prop definitions, which are prefixed with ':'
pub prop_definitions: Vec<PropDefinition>,
}
impl LayoutComponentDefinition {
/// Construct a definition for a layout component given its name in namespace:name format with an (initially) empty set of prop definitions
pub fn new(name: (String, String)) -> Self {
let prop_definitions = vec![];
Self { name, prop_definitions }
}
/// Add a prop definition (with its name, valid types, and default value) to this component definition
pub fn add_prop_definition(&mut self, prop_definition: PropDefinition) {
self.prop_definitions.push(prop_definition);
}
}
// ====================================================================================================
/// Abstract representation of a tag inside an abstract component with attributes and children
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutComponentTag {
/// Namespace and name of the tag's referenced component
pub name: (String, String),
/// Layout attributes, which are used by the layout engine
pub layout: LayoutAttributes,
/// Props on this tag, which are prefixed with ':'
pub props: Vec<Prop>,
/// The special `children` attribute, containing the inner elements of this tag
pub children: Option<Vec<NodeTree>>,
}
impl LayoutComponentTag {
/// Construct a tag in an XML layout component based on its referenced component name (in namespace:name format) and empty defaults
pub fn new(name: (String, String)) -> Self {
Self {
name,
layout: Default::default(),
children: None,
props: Vec::new(),
}
}
/// Provide a sequence of ASTs for this component's special `children` attribute
pub fn set_children(&mut self, children: Vec<NodeTree>) {
self.children = Some(children);
}
/// Add an XML tag attribute to this component (either a layout engine setting, a prop, or an event handler binding)
pub fn add_attribute(&mut self, attribute: Prop) {
// Prop argument (for reactive data system)
if attribute.name.len() > 1 && &attribute.name[..1] == ":" {
self.add_prop(attribute);
}
// Event handler attribute (for event system)
else if attribute.name.len() > 3 && &attribute.name[..3] == "on:" {
todo!("Event attributes not implemented yet");
}
// Layout attribute (for layout engine)
else {
self.add_layout_attribute(attribute);
}
}
/// Add an XML tag attribute to this component for a colon-prefixed prop
fn add_prop(&mut self, attribute: Prop) {
self.props.push(attribute);
}
/// Add an XML tag attribute to this component for a non-prefixed layout engine value
fn add_layout_attribute(&mut self, attribute: Prop) {
match &attribute.name[..] {
// Layout attributes, stored separately
"width" => self.layout.width = attribute.dimension(),
"height" => self.layout.height = attribute.dimension(),
"x-align" => self.layout.x_align = attribute.percent(),
"y-align" => self.layout.y_align = attribute.percent(),
"x-padding" => self.layout.padding.set_horizontal(attribute.dimension()),
"y-padding" => self.layout.padding.set_vertical(attribute.dimension()),
"padding" => self.layout.padding = attribute.box_dimensions(),
"x-gap" => self.layout.gap.set_horizontal(attribute.dimension()),
"y-gap" => self.layout.gap.set_vertical(attribute.dimension()),
"gap" => self.layout.gap = attribute.box_dimensions(),
_ => panic!("Unknown builtin attribute `{}`", attribute.name),
}
}
/// Print the layout tag (for debugging)
pub fn debug_print(&self) {
println!("Tag Node: {:#?}", self);
if let Some(ref children) = self.children {
for child in children {
for node in child.descendants() {
println!("> Descendant Node: {:#?}", node);
}
}
}
}
}
// ====================================================================================================
/// Name-value pair for a prop used in the prop-passing system, where the name is a `String` and the value sequence is a vector of `TypedValueOrVariableName`s
#[derive(Debug, Clone, PartialEq)]
pub struct Prop {
pub name: String,
pub value_sequence: Vec<TypedValueOrVariableName>,
}
impl Prop {
/// Construct a name-value pair representing an argument on a layout tag given its name and sequence of values
pub fn new(name: String, value_sequence: Vec<TypedValueOrVariableName>) -> Self {
Self { name, value_sequence }
}
/// Extract this attribute's values as typed values
fn values(self) -> Vec<TypedValue> {
self.value_sequence
.into_iter()
.map(|value| {
if let TypedValueOrVariableName::TypedValue(typed_value) = value {
typed_value
}
else {
todo!("Variable arguments are not yet supported")
}
})
.collect()
}
/// Convert this attribute's value into a single dimension
fn dimension(self) -> Dimension {
let values = self.values();
assert_eq!(values.len(), 1, "Expected a single value");
values[0].expect_dimension()
}
/// Extract a percentage from this attribute's value
fn percent(self) -> f64 {
match self.dimension() {
Dimension::Percent(value) => value,
_ => panic!("Expected a percentage"),
}
}
/// Convert this attribute's values into box dimensions
fn box_dimensions(self) -> BoxDimensions {
let values = self.values();
match values.len() {
1 => {
let value = values[0].expect_dimension();
BoxDimensions::all(value)
},
2 => {
let vertical = values[0].expect_dimension();
let horizontal = values[1].expect_dimension();
BoxDimensions::symmetric(vertical, horizontal)
},
4 => {
let top = values[0].expect_dimension();
let right = values[1].expect_dimension();
let bottom = values[2].expect_dimension();
let left = values[3].expect_dimension();
BoxDimensions::new(top, right, bottom, left)
},
_ => panic!("Expected 1, 2 or 4 values"),
}
}
}
// ====================================================================================================
/// Attributes used by the layout engine to calculate sizing and placement
#[derive(Clone, Debug, PartialEq)]
pub struct LayoutAttributes {
pub width: Dimension,
pub height: Dimension,
pub x_align: f64,
pub y_align: f64,
pub gap: BoxDimensions,
pub padding: BoxDimensions,
}
impl Default for LayoutAttributes {
/// Provide default values for dimensions, alignment, and outside spacing
fn default() -> Self {
let zero_box = BoxDimensions::all(Dimension::AbsolutePx(0.0));
Self {
width: Dimension::Inner,
height: Dimension::Inner,
x_align: 0.0,
y_align: 0.0,
gap: zero_box,
padding: zero_box,
}
}
}

View file

@ -1,146 +0,0 @@
use crate::color::Color;
use crate::layout_abstract_syntax::*;
/// Definition of a prop for a component, given in an attribute of the XML root tag
#[derive(Debug, Clone, PartialEq)]
pub struct PropDefinition {
// Name of the variable binding that can be used within the component in {{template tags}}
pub variable_name: String,
// Combinations of allowed sequences of types that can be passed to instances of this component
pub type_sequence_options: Vec<Vec<TypeName>>,
// A single sequence of default values that get used if an instance of this component never has the corresponding argument passed to it
pub type_sequence_default: Vec<TypedValue>,
}
impl PropDefinition {
/// Construct a prop definition for a variable accepted by a component definition, with the variable name, valid combinations of types, and the default value sequence
pub fn new(variable_name: String, valid_types: Vec<Vec<TypeName>>, default: Vec<TypedValue>) -> Self {
Self {
variable_name,
type_sequence_options: valid_types,
type_sequence_default: default,
}
}
}
// ====================================================================================================
/// Wrapper for either a `TypedValue` struct or the name of a prop
#[derive(Debug, Clone, PartialEq)]
pub enum TypedValueOrVariableName {
TypedValue(TypedValue),
VariableName(String),
}
// ====================================================================================================
/// All possible names for types of values in the reactive data and layout system
#[derive(Debug, Clone, PartialEq)]
pub enum TypeName {
Layout,
Integer,
Decimal,
AbsolutePx,
Percent,
PercentRemainder,
Inner,
Width,
Height,
TemplateString,
Color,
Bool,
None,
}
// ====================================================================================================
/// Concrete values for data in the various types allowed by the reactive data and layout system
#[derive(Debug, Clone, PartialEq)]
pub enum TypedValue {
Layout(Vec<NodeTree>),
Integer(i64),
Decimal(f64),
Dimension(Dimension),
TemplateString(Vec<TemplateStringSegment>),
Color(Color),
Bool(bool),
None,
}
impl TypedValue {
/// Converts this to a dimension, panics if not possible.
pub fn expect_dimension(&self) -> Dimension {
match self {
Self::Dimension(dimension) => *dimension,
_ => panic!("Expected a dimension"),
}
}
}
// ====================================================================================================
/// A piece of a template string, made up of many of these enums concatenated together in alternating order between `String` and `Argument`, where the latter is a value or variable name
#[derive(Debug, Clone, PartialEq)]
pub enum TemplateStringSegment {
String(String),
Argument(TypedValueOrVariableName),
}
// ====================================================================================================
/// A dimension is a measure along an axis.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Dimension {
/// Absolute value in pixels.
AbsolutePx(f64),
/// Percent of parent container size along the same axis.
Percent(f64),
/// Percent of free space remaining in parent container.
PercentRemainder(f64),
/// Minimum size required to fit the children.
Inner,
/// Size relative to the width of this component.
Width,
/// Size relative to the height of this component.
Height,
}
// ====================================================================================================
/// Dimensions along the four sides of a box layout
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct BoxDimensions {
pub top: Dimension,
pub right: Dimension,
pub bottom: Dimension,
pub left: Dimension,
}
impl BoxDimensions {
/// Construct new box dimensions, with values given for each side.
pub fn new(top: Dimension, right: Dimension, bottom: Dimension, left: Dimension) -> Self {
Self { top, right, bottom, left }
}
/// Construct new box dimensions, with same values used for top-bottom and left-right.
pub fn symmetric(vertical: Dimension, horizontal: Dimension) -> Self {
Self::new(vertical, horizontal, vertical, horizontal)
}
/// Construct new box dimensions with the same value for all sides.
pub fn all(value: Dimension) -> Self {
Self::new(value, value, value, value)
}
/// Sets the padding on the top and bottom sides.
pub fn set_vertical(&mut self, value: Dimension) {
self.top = value;
self.bottom = value;
}
/// Sets the padding on the left and right sides.
pub fn set_horizontal(&mut self, value: Dimension) {
self.left = value;
self.right = value;
}
}

View file

@ -1,318 +0,0 @@
use crate::color::Color;
use crate::color_palette::ColorPalette;
use crate::layout_abstract_types::*;
use crate::layout_system::*;
pub struct AttributeParser {
capture_attribute_prop_definition_regex: regex::Regex,
capture_attribute_type_sequences_regex: regex::Regex,
match_integer_regex: regex::Regex,
match_decimal_regex: regex::Regex,
split_by_string_templates_regex: regex::Regex,
capture_color_name_in_palette_regex: regex::Regex,
}
impl AttributeParser {
// Prebuild all the regex patterns
pub fn new() -> Self {
let capture_attribute_prop_definition_regex: regex::Regex = regex::Regex::new(
// Prop definition: ?: (?, ... | ...) = ?
r"^\s*(\w*)\s*(:)\s*(\()\s*((?:(?:\w+)(?:\s*,\s*\w+)*)(?:\s*\|\s*(?:(?:\w+)(?:\s*,\s*\w+)*))*)\s*(\))\s*(=)\s*([\s\w'\[\]@%\-.,]+|`[^`]*`|\[\[.*\]\])\s*$",
)
.unwrap();
let capture_attribute_type_sequences_regex: regex::Regex = regex::Regex::new(concat!(
// Argument: {{?}}
r#"^\s*(\{\{)\s*(\w*)\s*(\}\})\s*$|"#,
// Layout: [[?]]
r#"^\s*(\[\[)\s*(.*)\s*(\]\])\s*$|"#,
// Integer: ?
r#"^\s*(-?\d+)\s*$|"#,
// Decimal: ?
r#"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$|"#,
// AbsolutePx: ?px
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))([Pp][Xx])\s*$|"#,
// Percent: ?%
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(%)\s*$|"#,
// PercentRemainder: ?@
r#"^\s*(-?(?:(?:\d+(?:\.\d*)?)|(?:\d*(?:\.\d+))))(@)\s*$|"#,
// Inner: inner
r#"^\s*([Ii][Nn][Nn][Ee][Rr])\s*$|"#,
// Width: width
r#"^\s*([Ww][Ii][Dd][Tt][Hh])\s*$|"#,
// Height: height
r#"^\s*([Hh][Ee][Ii][Gg][Hh][Tt])\s*$|"#,
// TemplateString: `? ... {{?}} ...`
r#"^\s*(`)(.*)(`)\s*$|"#,
// Color: [?]
r#"^\s*(\[)(.*)(\])\s*$|"#,
// Bool: true/false
r#"^\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])\s*$|"#,
// None: none
r#"^\s*([Nn][Oo][Nn][Ee])\s*$"#,
))
.unwrap();
let match_integer_regex = regex::Regex::new(r"^\s*(-?\d+)\s*$").unwrap();
let match_decimal_regex = regex::Regex::new(r"^\s*(-?(?:(?:\d+\.\d*)|(?:\d*\.\d+)))\s*$").unwrap();
let split_by_string_templates_regex = regex::Regex::new(r"\{\{|\}\}").unwrap();
let capture_color_name_in_palette_regex = regex::Regex::new(r"\s*'(.*)'\s*").unwrap();
Self {
capture_attribute_prop_definition_regex,
capture_attribute_type_sequences_regex,
match_integer_regex,
match_decimal_regex,
split_by_string_templates_regex,
capture_color_name_in_palette_regex,
}
}
pub fn parse_attribute_argument_types(&self, input: &str) -> Vec<TypedValueOrVariableName> {
let attribute_types = input.split(",").map(|piece| piece.trim()).collect::<Vec<&str>>();
let list = attribute_types
.iter()
.map(|attribute_type| self.parse_attribute_argument_type(attribute_type))
.collect::<Vec<TypedValueOrVariableName>>();
list
}
pub fn parse_attribute_argument_type(&self, attribute_type: &str) -> TypedValueOrVariableName {
// Match with the regular expression
let captures = self
.capture_attribute_type_sequences_regex
.captures(attribute_type)
.map(|captures| captures.iter().skip(1).flat_map(|c| c).map(|c| c.as_str()).collect::<Vec<_>>());
// Match against the captured values as a list of tokens
let tokens = captures.as_ref().map(|c| c.as_slice());
match tokens {
// Variable name: {{?}}
Some(["{{", name, "}}"]) => TypedValueOrVariableName::VariableName(String::from(*name)),
// Layout: [[?]]
Some(["[[", xml_syntax, "]]"]) => {
// Remove any whitespace in order to test if any XML syntax is present
let trimmed = xml_syntax.trim();
// Build either an empty vector (for empty XML input) or a vector with the one parsed XML fragment
let layout_entries = if trimmed.len() == 0 {
vec![]
}
else {
let unescaped = Self::unescape_xml(trimmed);
let parsed = LayoutSystem::parse_xml_node(&self, &unescaped[..], false).unwrap();
// Put the single parsed node in a vector (TODO: this should set any number of parsed nodes once `parse_xml_node` becomes `parse_xml_nodes`)
vec![parsed]
};
// Return the `Layout` typed value with the empty vector or vector with the parsed XML fragment
TypedValueOrVariableName::TypedValue(TypedValue::Layout(layout_entries))
},
// Integer: ?
Some([value]) if self.match_integer_regex.is_match(value) => {
let integer = value
.parse::<i64>()
.expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
TypedValueOrVariableName::TypedValue(TypedValue::Integer(integer))
},
// Decimal: ?
Some([value]) if self.match_decimal_regex.is_match(value) => {
let decimal = value
.parse::<f64>()
.expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
TypedValueOrVariableName::TypedValue(TypedValue::Decimal(decimal))
},
// AbsolutePx: px
Some([value, px]) if px.eq_ignore_ascii_case("px") => {
let pixels = value
.parse::<f64>()
.expect(&format!("Invalid value `{}` specified in the attribute type`{}` when parsing XML layout", value, attribute_type)[..]);
let dimension = Dimension::AbsolutePx(pixels);
TypedValueOrVariableName::TypedValue(TypedValue::Dimension(dimension))
},
// Percent: ?%
Some([value, "%"]) => {
let percent = value
.parse::<f64>()
.expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
let dimension = Dimension::Percent(percent);
TypedValueOrVariableName::TypedValue(TypedValue::Dimension(dimension))
},
// PercentRemainder: ?@
Some([value, "@"]) => {
let percent_remainder = value
.parse::<f64>()
.expect(&format!("Invalid value `{}` specified in the attribute type `{}` when parsing XML layout", value, attribute_type)[..]);
let dimension = Dimension::PercentRemainder(percent_remainder);
TypedValueOrVariableName::TypedValue(TypedValue::Dimension(dimension))
},
// Inner: inner
Some([inner]) if inner.eq_ignore_ascii_case("inner") => TypedValueOrVariableName::TypedValue(TypedValue::Dimension(Dimension::Inner)),
// Width: width
Some([width]) if width.eq_ignore_ascii_case("width") => TypedValueOrVariableName::TypedValue(TypedValue::Dimension(Dimension::Width)),
// Height: height
Some([height]) if height.eq_ignore_ascii_case("height") => TypedValueOrVariableName::TypedValue(TypedValue::Dimension(Dimension::Height)),
// TemplateString: `? ... {{?}} ...`
Some(["`", string, "`"]) => {
let segments = self.parse_text_template_sequence(string);
TypedValueOrVariableName::TypedValue(TypedValue::TemplateString(segments))
},
// Color: [?]
Some(["[", color_name, "]"]) => {
let color = match self.capture_color_name_in_palette_regex.captures(color_name) {
Some(captures) => {
let palette_color = captures
.get(1)
.expect(
&format!(
"Invalid palette color name `{}` specified in the attribute type `{}` when parsing XML layout",
color_name, attribute_type
)[..],
)
.as_str();
ColorPalette::lookup_palette_color(palette_color).into_color_srgb()
},
None => {
let parsed = color_name.parse::<css_color_parser::Color>();
let css_color = parsed.expect(
&format!(
"Invalid CSS color name `{}` specified in the attribute type `{}` when parsing XML layout",
color_name, attribute_type
)[..],
);
Color::new(
css_color.r as f32 / 255.0,
css_color.g as f32 / 255.0,
css_color.b as f32 / 255.0,
css_color.a as f32 / 255.0,
)
},
};
TypedValueOrVariableName::TypedValue(TypedValue::Color(color))
},
// Bool: true/false
Some([true_or_false]) if true_or_false.eq_ignore_ascii_case("true") || true_or_false.eq_ignore_ascii_case("false") => {
let boolean = true_or_false.eq_ignore_ascii_case("true");
TypedValueOrVariableName::TypedValue(TypedValue::Bool(boolean))
},
// None: none
Some([none]) if none.eq_ignore_ascii_case("none") => TypedValueOrVariableName::TypedValue(TypedValue::None),
// Unrecognized type pattern
_ => panic!("Invalid attribute type `{}` when parsing XML layout", attribute_type),
}
}
pub fn parse_attribute_prop_definition(&self, attribute_declaration: &str) -> PropDefinition {
// Match with the regular expression
let captures = self
.capture_attribute_prop_definition_regex
.captures(attribute_declaration)
.map(|captures| captures.iter().skip(1).flat_map(|c| c).map(|c| c.as_str()).collect::<Vec<_>>());
// Match against the captured values as a list of tokens
let tokens = captures.as_ref().map(|c| c.as_slice());
match tokens {
// Prop definition: ?: (?, ... | ...) = ?
Some([name, ":", "(", raw_types_list, ")", "=", default_value]) => {
// Variable name bound in the prop definition
let name = String::from(*name);
// Split the type sequences up into a list of options separated by vertical bars
let type_sequence_options = String::from(*raw_types_list)
.split("|")
.map(|group| {
// Split each type sequence into individual types separated by commas
group
.split(",")
.map(|individual_type| {
// Remove any whitespace around the type
let individual_type = individual_type.trim();
// Return the case-insensitive TypeName enum for the individual type
match &individual_type.to_ascii_lowercase()[..] {
"layout" => TypeName::Layout,
"integer" => TypeName::Integer,
"decimal" => TypeName::Decimal,
"absolutepx" => TypeName::AbsolutePx,
"percent" => TypeName::Percent,
"percentremainder" => TypeName::PercentRemainder,
"inner" => TypeName::Inner,
"width" => TypeName::Width,
"height" => TypeName::Height,
"templatestring" => TypeName::TemplateString,
"color" => TypeName::Color,
"bool" => TypeName::Bool,
"none" => TypeName::None,
_ => panic!(
"Invalid type `{}` specified in the attribute type `{}` when parsing XML layout",
individual_type, attribute_declaration
),
}
})
.collect::<Vec<TypeName>>()
})
.collect::<Vec<Vec<TypeName>>>();
// Split the provided default values into a sequence
let default_type_sequence = default_value
.split(",")
.map(|individual_type| match self.parse_attribute_argument_type(individual_type) {
TypedValueOrVariableName::TypedValue(type_value) => type_value,
TypedValueOrVariableName::VariableName(variable_value) => {
panic!(
"Found the default variable name `{:?}` in the attribute declaration `{}` (which only allows typed values) when parsing XML layout",
variable_value, attribute_declaration
);
},
})
.collect::<Vec<TypedValue>>();
// TODO: Verify the default types match the specified allowed types
// Return the prop definition
PropDefinition::new(name, type_sequence_options, default_type_sequence)
},
// Unrecognized type pattern
_ => panic!("Invalid attribute attribute declaration `{}` when parsing XML layout", attribute_declaration),
}
}
/// Extract {{template tags}} from surrounding text and return a vector alternating between text and the argument
pub fn parse_text_template_sequence(&self, input_text: &str) -> Vec<TemplateStringSegment> {
let mut segments = Vec::<TemplateStringSegment>::new();
let mut is_template = false;
// Alternate between string and handlebars, always starting wtih string even if empty, and push abstract tokens of non-empty ones to the TemplateString sequence
for part in self.split_by_string_templates_regex.split(input_text) {
// Push only non-empty template string segments (a String or Argument)
if !part.is_empty() {
// Based on whether we are alternating to a string or template, push the appropriate abstract token
let segment = match is_template {
false => TemplateStringSegment::String(String::from(part)),
true => TemplateStringSegment::Argument(TypedValueOrVariableName::VariableName(String::from(part))),
};
segments.push(segment);
}
// The next iteration will switch from a template to a string or vice versa
is_template = !is_template;
}
segments
}
/// Replace escape characters in an XML string, only supports `&, <, >, ", '`
fn unescape_xml(xml: &str) -> String {
// Find and replace each escape character, starting with `&` to avoid unescaping other escape sequences
xml.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&quot;", "\"")
.replace("apos;", "'")
.replace("&#39;", "'")
}
}

View file

@ -1,379 +0,0 @@
use crate::layout_abstract_syntax::*;
use crate::layout_abstract_types::*;
use crate::layout_attribute_parser::*;
use crate::resource_cache::ResourceCache;
use crate::window_dom::*;
use std::collections::HashSet;
use std::fs;
use std::io;
pub struct LayoutSystem<'a> {
windows: Vec<WindowDom<'a>>,
loaded_components: ResourceCache<FlatComponent>,
attribute_parser: AttributeParser,
}
impl<'a> LayoutSystem<'a> {
/// Construct the `LayoutSystem` with zero windows, an empty cache of component XML layouts, and an `AttributeParser` with its regex parsers
pub fn new() -> Self {
Self {
windows: vec![],
loaded_components: ResourceCache::new(),
attribute_parser: AttributeParser::new(),
}
}
/// Load and construct a new window from a layout component
pub fn add_window(&'a mut self, name: (&str, &str), window_size: (u32, u32)) {
// Preload the component and its dependencies
self.preload_component(name)
.expect(&format!("Failure loading layout component '{}'", Self::component_name(name))[..]);
// Get the now-loaded component's namespace:name
let window_root_component_name = Self::component_name(name);
// Construct the window and save it
let new_window = WindowDom::new(&window_root_component_name[..], window_size, &self.loaded_components);
self.windows.push(new_window);
}
/// Preload and cache a component by its namespace and name, then recursively explore and repeat for its descendants
pub fn preload_component(&mut self, name: (&str, &str)) -> io::Result<()> {
// Load and parse the XML file's AST for the visited tag
let xml_path = Self::layout_xml_path(name);
let mut component = Self::parse_xml_component(&self.attribute_parser, &xml_path[..], true)?;
// Keep track of it being loaded to prevent duplicate work during the recursive traversal
let mut already_loaded_layouts = HashSet::new();
already_loaded_layouts.insert(Self::component_name(name));
// Parse and cache components recursively for all tags referenced within this root component
self.explore_component(&mut component, &mut already_loaded_layouts);
// Save this loaded root-level component to the cache
let component_name = Self::component_name(name);
self.loaded_components.set(&component_name[..], component);
// Success
Ok(())
}
/// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given flat component
fn explore_component(&mut self, component: &mut FlatComponent, already_loaded_layouts: &mut HashSet<String>) {
// Go through each direct child in the list that makes up flat component
for child_tag in &component.child_components {
self.explore_component_tag(child_tag, already_loaded_layouts);
}
// Go through each prop definition and preload any default values of layouts
for definition in &component.own_info.prop_definitions {
for default in definition.type_sequence_default.iter() {
if let TypedValue::Layout(layouts) = default {
for layout in layouts {
match &*layout.borrow() {
LayoutComponentNode::Tag(tag) => self.explore_component_tag(tag, already_loaded_layouts),
LayoutComponentNode::Text(_) => {},
}
}
}
}
}
}
/// Preload and cache every XML component file referenced by tags within a recursive traversal of descendants in the given component tag
fn explore_component_tag(&mut self, tag: &LayoutComponentTag, already_loaded_layouts: &mut HashSet<String>) {
// Determine the cache key of form namespace:name
let (name, namespace) = &tag.name;
let key = Self::component_name((&name[..], &namespace[..]));
// Load the new component if it isn't already preloaded
if !already_loaded_layouts.contains(&key[..]) && self.loaded_components.get(&key[..]).is_none() {
// Load and parse the component XML file for the visited tag
let xml_path = Self::layout_xml_path((&name[..], &namespace[..]));
let mut component = Self::parse_xml_component(&self.attribute_parser, &xml_path[..], true).unwrap();
// Keep track of it being loaded to prevent duplicate work
let key_copy = key.clone();
already_loaded_layouts.insert(key);
// Recursively explore the newly loaded component
self.explore_component(&mut component, already_loaded_layouts);
// Save the loaded component to the cache
self.loaded_components.set(&key_copy[..], component);
}
// Expore the props with values of type Layout
for argument in &tag.props {
for value in &argument.value_sequence {
if let TypedValueOrVariableName::TypedValue(TypedValue::Layout(layouts)) = value {
for layout in layouts {
match &*layout.borrow() {
LayoutComponentNode::Tag(component_tag) => self.explore_component_tag(component_tag, already_loaded_layouts),
LayoutComponentNode::Text(_) => {},
}
}
}
}
}
// Explore the tree of the `children` elements stored in this component
if let Some(ref children) = tag.children {
for child_node in children.iter() {
for descendant in child_node.descendants() {
match &*descendant.borrow() {
LayoutComponentNode::Tag(component_tag) => self.explore_component_tag(component_tag, already_loaded_layouts),
LayoutComponentNode::Text(_) => {},
}
}
}
}
}
/// Parse an XML component all the way into a flat component structure
pub fn parse_xml_component(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool) -> io::Result<FlatComponent> {
println!("Parsing XML Component: {}", path_or_source);
let parsed_tree = &mut Self::parse_xml_tree(attribute_parser, path_or_source, is_path_not_source, true)?;
let flat_tree = Self::flatten_component_tree(parsed_tree);
Ok(flat_tree)
}
/// Parse a fragment of XML layout syntax with a tree of tags (currently only supports a single root node, should eventually implement returning a vector of them)
pub fn parse_xml_node(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool) -> io::Result<NodeTree> {
let parsed_tree = Self::parse_xml_tree(attribute_parser, path_or_source, is_path_not_source, false)?;
Ok(Self::node_tree_from_node_or_def_tree(&parsed_tree))
}
/// Flatten a full XML component AST into a vector of the immediate children and put the descendants of those nodes into `children` attributes
fn flatten_component_tree(tree: &mut NodeOrDefTree) -> FlatComponent {
let own_info = match &*tree.borrow() {
LayoutComponentNodeOrDefinition::LayoutComponentDefinition(definition) => definition.clone(),
LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(_)) => panic!("Tag node found in place of component definition"),
LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Text(_)) => panic!("Text node found in place of component definition"),
};
// Turn all the tag nodes (but not text nodes) into a list of flat child components (with their descendant trees in their `children` attributes)
let child_components = tree
// Get the direct children from this tree node
.children()
// Clone each child abstract tag node (ignoring text nodes) with each of their descendants added to their `children` attribute variable
.filter_map(|child_node| {
// Filter out text nodes because they make no sense as child components
let mut cloned_tag = match &*child_node.borrow() {
LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(child_tag)) => child_tag.clone(),
LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Text(_)) => return None,
LayoutComponentNodeOrDefinition::LayoutComponentDefinition(_) => panic!("Component definition found in place of tag node"),
};
// Clone the tree for this child as `LayoutComponentNode`s and turn its children into a vector, then set that vector as the `children` attribute
let node_within_root = Self::node_tree_from_node_or_def_tree(&child_node);
let children = node_within_root.children().map(|mut child| {
// Child must be detached in order to live on its own in the vector, otherwise it will be cleaned up when its (former) parent is dropped
child.detach();
child
}).collect::<Vec<_>>();
cloned_tag.set_children(children);
// Return this `LayoutComponentTag` within the component's root definition tag
Some(cloned_tag)
})
.collect::<Vec<_>>();
// Build and return the resulting flat component made from the cloned data for its `own_info` and `child_components`
FlatComponent::new(own_info, child_components)
}
/// Get an AST root node representing a parsed XML component file or XML source code
pub fn parse_xml_tree(attribute_parser: &AttributeParser, path_or_source: &str, is_path_not_source: bool, component_declaration: bool) -> io::Result<NodeOrDefTree> {
// XML component file markup source code
let (path, source) = if is_path_not_source {
(path_or_source, fs::read_to_string(path_or_source)?)
}
else {
("[Inline Attribute XML]", String::from(path_or_source))
};
// XML document parser that feeds token-by-token through the file
let parser = xmlparser::Tokenizer::from(&source[..]);
// Node stack used to collect descendant nodes while reading deeper into the tree until each reaches its closing tag
let mut stack: Vec<NodeOrDefTree> = Vec::new();
// Opening XML tag used to collect the tag name and its various attributes
let mut current_opening_tag: Option<LayoutComponentNodeOrDefinition> = None;
// Top-level node that is popped from the stack when the closing tag is reached at the end of the XML document
let mut final_result: Option<NodeOrDefTree> = None;
for token_result in parser {
let token = token_result.expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]);
match token {
// Beginning of an opening tag (<NAMESPACE:NAME ...)
xmlparser::Token::ElementStart { prefix, local, .. } => {
// Get the supplied namespace and tag name as owned strings
let name = (String::from(prefix.as_str()), String::from(local.as_str()));
// If this is the root element and we're parsing a component file, the root tag is the component definition
if stack.is_empty() && component_declaration {
// Construct and store the component definition while attributes are added until its opening tag ends
let definition = LayoutComponentDefinition::new(name);
current_opening_tag = Some(LayoutComponentNodeOrDefinition::LayoutComponentDefinition(definition));
}
// Otherwise, we're parsing a node inside the root or at the root of a fragment of XML layout syntax
else {
// Construct and store the component node while attributes are added until the opening (or self-closing) tag ends
let tag_node = LayoutComponentNode::new_tag(name);
current_opening_tag = Some(LayoutComponentNodeOrDefinition::LayoutComponentNode(tag_node));
}
},
// Any attributes within the current opening tag (... ATTRIBUTE="VALUE" ...)
xmlparser::Token::Attribute { prefix, local, value, .. } => {
// Check if the attribute has an empty prefix (thus, only a colon)
let colon_prefixed = prefix.start() > 0 && (prefix.start() == prefix.end());
// Set the name to the given name, possibly with a prepended colon
let name = if colon_prefixed {
let slice = local.as_str();
let mut string = String::with_capacity(slice.len() + 1);
string.push(':');
string.push_str(slice);
string
}
else {
String::from(local.as_str())
};
// Set the value to an ordinary string slice of the given value
let value = value.as_str();
// Add the new attribute to the current yet-to-be-closed element
match &mut current_opening_tag {
// Add this attribute as a definition of a prop to the current root-level component definition tag
Some(LayoutComponentNodeOrDefinition::LayoutComponentDefinition(definition)) => {
let prop_definition = attribute_parser.parse_attribute_prop_definition(value);
definition.add_prop_definition(prop_definition);
},
// Add this attribute as a prop to the current tag
Some(LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::Tag(tag))) => {
let parsed_attributes = attribute_parser.parse_attribute_argument_types(value);
let attribute_argument = Prop::new(name, parsed_attributes);
tag.add_attribute(attribute_argument);
},
// It should be impossible to add an attribute when there is no opening tag in progress
_ => unreachable!(),
}
},
// Either the end of the opening tag (...>) or the end of a self-closing tag (.../>) or an entire closing tag (</NAMESPACE:NAME>)
xmlparser::Token::ElementEnd { end, .. } => {
match end {
// After adding any attributes, this element's opening tag ends (...>)
xmlparser::ElementEnd::Open => {
// After adding any attributes, we are now a layer deeper in the stack of yet-to-be-closed descendants
let complete_opening_tag = current_opening_tag.take().unwrap();
let tree_node = rctree::Node::new(complete_opening_tag);
stack.push(tree_node);
},
// After adding any attributes, this element's self-closing tag ends (.../>)
xmlparser::ElementEnd::Empty => {
// Because a self-closing element does not go deeper, attach this now-complete node directly to its parent
let parent_node = stack.last_mut().expect(&format!("Invalid syntax when parsing XML layout in component: {}", path)[..]);
let complete_self_closing_tag = current_opening_tag.take().unwrap();
let tree_node = rctree::Node::new(complete_self_closing_tag);
parent_node.append(tree_node);
},
// After visiting any descendants inside the opening tag, finally the closing tag is reached (</NAMESPACE:NAME>)
xmlparser::ElementEnd::Close(..) => {
// Pop the element now that descendants have been parsed and we make our way back up the tree one level
let closed_node_with_descendants = stack
.pop()
.expect(&format!("Encountered extra closing tag when parsing XML layout in component: {}", path)[..]);
// Append this now-complete node to its parent, unless there is no parent, in which case we save this root node as the final result
match stack.last_mut() {
// If a parent node exists
Some(parent_node) => {
parent_node.append(closed_node_with_descendants);
},
// If this is the root node
None => {
match final_result {
// Save the root element as the final result
None => final_result = Some(closed_node_with_descendants),
// There can only be one root element in the XML document, but this isn't the first one encountered
Some(_) => panic!("Encountered multiple root-level tags when parsing XML layout in component: {}", path),
}
},
}
},
}
},
// A text node in the space between sibling elements (... SOME TEXT ...)
xmlparser::Token::Text { text } => {
// Trim any whitespace from around the string
let text_string = String::from(text.as_str().trim());
// If the string isn't all whitespace, append a new text node to the parent
if !text_string.is_empty() {
// Get the tree node which contains this text
let parent_node = stack
.last_mut()
.expect(&format!("Encountered text outside the root tag when parsing XML layout in component: {}", path)[..]);
// Construct a text node with the provided text
let text_template_sequence = attribute_parser.parse_text_template_sequence(&text_string[..]);
let abstract_text_node = LayoutComponentNodeOrDefinition::LayoutComponentNode(LayoutComponentNode::new_text(text_template_sequence));
// Put the text node in a new tree node
let tree_node = rctree::Node::new(abstract_text_node);
// Attach the new text node on the parent in the tree which contains this text
parent_node.append(tree_node);
}
},
_ => {},
}
}
// Return the final result or throw an error
match final_result {
None => panic!("Invalid syntax when parsing XML layout in component: {}", path),
Some(tree) => Ok(tree),
}
}
/// Get a string in `namespace:name` format (or just `name` for primitives) given a namespace and component name
pub fn component_name(name: (&str, &str)) -> String {
let (namespace, file) = name;
if namespace.len() > 0 {
format!("{}:{}", namespace, file)
}
else {
String::from(file)
}
}
/// Get the XML file path given a namespace and component name
fn layout_xml_path(name: (&str, &str)) -> String {
let (namespace, file) = name;
if namespace.len() > 0 {
format!("gui/{}/{}.xml", namespace, file)
}
else {
format!("gui/{}.xml", file)
}
}
/// Convert every element in the tree of `LayoutComponentNodeOrDefinition` wrapper enums into unwrapped `LayoutComponentNode` structs
fn node_tree_from_node_or_def_tree(layout_component_node_or_definition: &NodeOrDefTree) -> NodeTree {
// Unwrap the `LayoutComponentNode` from the root element's value
let cloned_node_data = match &*layout_component_node_or_definition.borrow() {
LayoutComponentNodeOrDefinition::LayoutComponentNode(node) => node.clone(),
LayoutComponentNodeOrDefinition::LayoutComponentDefinition(_) => panic!("Found an unexpected component definition while expecting a node"),
};
// Build a new tree of the correct type with the unwrapped data as its root value
let mut tree_result = rctree::Node::new(cloned_node_data);
// Go through all the direct children of the old tree and append the new recursively converted trees to match the shape of the old tree
for tree_node in layout_component_node_or_definition.children() {
tree_result.append(Self::node_tree_from_node_or_def_tree(&tree_node));
}
tree_result
}
}

View file

@ -1,38 +0,0 @@
mod application;
mod color;
mod color_palette;
mod draw_command;
mod gui_attributes;
mod gui_node;
mod layout_abstract_syntax;
mod layout_abstract_types;
mod layout_attribute_parser;
mod layout_system;
mod pipeline;
mod resource_cache;
mod shader_stage;
mod texture;
mod window_dom;
mod window_events;
use application::Application;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
fn main() {
// Display graphics API errors (requires Vulkan SDK is installed)
#[cfg(feature = "debug")]
env_logger::init();
// Handles all window events, user input, and redraws
let event_loop = EventLoop::new();
// Application window in the operating system
let window = WindowBuilder::new().with_title("Graphite").build(&event_loop).unwrap();
// Initialize the render pipeline
let app = Application::new(&window);
// Begin the application lifecycle
app.begin_lifecycle(event_loop, window);
}

View file

@ -1,177 +0,0 @@
use crate::resource_cache::ResourceCache;
use crate::shader_stage;
use std::mem;
pub struct Pipeline {
pub bind_group_layout: wgpu::BindGroupLayout,
pub render_pipeline: wgpu::RenderPipeline,
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
swap_chain_color_format: wgpu::TextureFormat,
extra_layouts: Vec<&wgpu::BindGroupLayout>,
shader_cache: &mut ResourceCache<wgpu::ShaderModule>,
shader_pair_path: (&str, &str),
) -> Self {
// Load the vertex and fragment shaders
let shader_pair = Pipeline::get_shader_pair(device, shader_cache, shader_pair_path);
// Prepare a bind group layout for the GUI element's texture and form factor data
let bind_group_layout = Pipeline::build_bind_group_layout(
device,
&vec![
wgpu::BindingType::UniformBuffer { dynamic: false },
wgpu::BindingType::SampledTexture {
dimension: wgpu::TextureViewDimension::D2,
component_type: wgpu::TextureComponentType::Float,
multisampled: false,
},
wgpu::BindingType::Sampler { comparison: false },
],
);
// Combine all bind group layouts
let mut bind_group_layouts = vec![&bind_group_layout];
bind_group_layouts.append(&mut extra_layouts.clone());
// Construct the pipeline
let render_pipeline = Pipeline::build_pipeline(device, swap_chain_color_format, bind_group_layouts, shader_pair);
Self {
bind_group_layout,
render_pipeline,
}
}
pub fn get_shader_pair<'a>(
device: &wgpu::Device,
shader_cache: &'a mut ResourceCache<wgpu::ShaderModule>,
shader_pair_path: (&str, &str),
) -> (&'a wgpu::ShaderModule, &'a wgpu::ShaderModule) {
// If uncached, construct a vertex shader loaded from its source code file
if shader_cache.get(shader_pair_path.0).is_none() {
let vertex_shader_module = shader_stage::compile_from_glsl(device, shader_pair_path.0, glsl_to_spirv::ShaderType::Vertex).unwrap();
shader_cache.set(shader_pair_path.0, vertex_shader_module);
}
// If uncached, construct a fragment shader loaded from its source code file
if shader_cache.get(shader_pair_path.1).is_none() {
let fragment_shader_module = shader_stage::compile_from_glsl(&device, shader_pair_path.1, glsl_to_spirv::ShaderType::Fragment).unwrap();
shader_cache.set(shader_pair_path.1, fragment_shader_module);
}
// Get the shader pair
let vertex_shader = shader_cache.get(shader_pair_path.0).unwrap();
let fragment_shader = shader_cache.get(shader_pair_path.1).unwrap();
(vertex_shader, fragment_shader)
}
pub fn build_bind_group_layouts(device: &wgpu::Device, bind_group_layouts: &Vec<Vec<wgpu::BindingType>>) -> Vec<wgpu::BindGroupLayout> {
bind_group_layouts
.into_iter()
.map(|layout_entry| Self::build_bind_group_layout(device, layout_entry))
.collect::<Vec<_>>()
}
pub fn build_bind_group_layout(device: &wgpu::Device, bind_group_layout: &Vec<wgpu::BindingType>) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
bindings: bind_group_layout
.into_iter()
.enumerate()
.map(|(index, binding_type)| wgpu::BindGroupLayoutEntry {
binding: index as u32,
visibility: wgpu::ShaderStage::all(),
ty: binding_type.clone(),
})
.collect::<Vec<_>>()
.as_slice(),
})
}
pub fn build_binding_staging_buffer<T: bytemuck::Pod>(device: &wgpu::Device, resource: &T) -> wgpu::Buffer {
// Construct a staging buffer with the binary uniform struct data
device.create_buffer_with_data(bytemuck::cast_slice(&[*resource]), wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST)
}
pub fn build_binding_resource(resource_buffer: &wgpu::Buffer) -> wgpu::BindingResource {
// Return the buffer as a binding resource
wgpu::BindingResource::Buffer {
buffer: resource_buffer,
range: 0..std::mem::size_of_val(resource_buffer) as wgpu::BufferAddress,
}
}
pub fn build_bind_group(device: &wgpu::Device, bind_group_layout: &wgpu::BindGroupLayout, binding_resources: Vec<wgpu::BindingResource>) -> wgpu::BindGroup {
let bindings = binding_resources
.into_iter()
.enumerate()
.map(|(index, binding_resource)| wgpu::Binding {
binding: index as u32,
resource: binding_resource,
})
.collect::<Vec<_>>();
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: bind_group_layout,
bindings: bindings.as_slice(),
label: None,
})
}
pub fn build_pipeline(
device: &wgpu::Device,
swap_chain_color_format: wgpu::TextureFormat,
bind_group_layouts: Vec<&wgpu::BindGroupLayout>,
shader_pair: (&wgpu::ShaderModule, &wgpu::ShaderModule),
) -> wgpu::RenderPipeline {
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: bind_group_layouts.as_slice(),
});
let (vertex_shader, fragment_shader) = shader_pair;
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: &render_pipeline_layout,
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: vertex_shader,
entry_point: "main",
},
fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
module: fragment_shader,
entry_point: "main",
}),
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
front_face: wgpu::FrontFace::Ccw,
cull_mode: wgpu::CullMode::None,
depth_bias: 0,
depth_bias_slope_scale: 0.0,
depth_bias_clamp: 0.0,
}),
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::ColorStateDescriptor {
format: swap_chain_color_format,
color_blend: wgpu::BlendDescriptor::REPLACE,
alpha_blend: wgpu::BlendDescriptor::REPLACE,
write_mask: wgpu::ColorWrite::ALL,
}],
depth_stencil_state: None,
vertex_state: wgpu::VertexStateDescriptor {
index_format: wgpu::IndexFormat::Uint16,
vertex_buffers: &[wgpu::VertexBufferDescriptor {
stride: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[wgpu::VertexAttributeDescriptor {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float2,
}],
}],
},
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
})
}
}

View file

@ -1,49 +0,0 @@
use std::collections::HashMap;
#[derive(Copy, Clone, PartialEq, Debug)]
struct CacheID {
index: usize,
}
impl CacheID {
fn new(index: usize) -> Self {
Self { index }
}
}
pub struct ResourceCache<T> {
pub resources: Vec<T>,
name_to_id: HashMap<String, CacheID>,
}
impl<T> ResourceCache<T> {
pub fn new() -> Self {
let resources = Vec::new();
let name_to_id = HashMap::new();
Self { resources, name_to_id }
}
#[allow(dead_code)]
pub fn get(&self, name: &str) -> Option<&T> {
match self.name_to_id.get(name) {
Some(id) => self.resources.get(id.index),
None => None,
}
}
#[allow(dead_code)]
pub fn set(&mut self, name: &str, resource: T) {
match self.name_to_id.get(name) {
Some(id) => {
self.resources[id.index] = resource;
},
None => {
let last_index = self.name_to_id.len();
let id = CacheID::new(last_index);
self.name_to_id.insert(String::from(name), id);
self.resources.push(resource);
},
}
}
}

View file

@ -1,17 +0,0 @@
use std::fs;
use std::io;
pub fn compile_from_glsl(device: &wgpu::Device, path: &str, shader_type: glsl_to_spirv::ShaderType) -> io::Result<wgpu::ShaderModule> {
let source = fs::read_to_string(path)?;
let spirv = match glsl_to_spirv::compile(&source[..], shader_type) {
Ok(spirv_output) => spirv_output,
Err(message) => {
println!("Error compiling GLSL to SPIRV shader: {}", message);
panic!("{}", message);
},
};
let compiled = wgpu::read_spirv(spirv)?;
let shader = device.create_shader_module(&compiled);
Ok(shader)
}

View file

@ -1,106 +0,0 @@
use crate::resource_cache::ResourceCache;
use image::GenericImageView;
use std::fs;
pub struct Texture {
pub texture: wgpu::Texture,
pub texture_view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl Texture {
pub fn cached_load<'a>(device: &wgpu::Device, queue: &mut wgpu::Queue, path: &str, texture_cache: &'a mut ResourceCache<Texture>) -> &'a Texture {
// If uncached, construct a texture loaded from the image file
if texture_cache.get(path).is_none() {
let texture = Texture::from_filepath(device, queue, path).unwrap();
texture_cache.set(path, texture);
}
texture_cache.get(path).unwrap()
}
pub fn from_filepath(device: &wgpu::Device, queue: &mut wgpu::Queue, path: &str) -> Result<Self, failure::Error> {
// Read the raw bytes from the specified file
let bytes = fs::read(path)?;
// Construct and return a Texture from the bytes
Texture::from_bytes(device, queue, &bytes[..])
}
pub fn from_bytes(device: &wgpu::Device, queue: &mut wgpu::Queue, bytes: &[u8]) -> Result<Self, failure::Error> {
// Create an image with the Image library
let image = image::load_from_memory(bytes)?;
// Construct and return a Texture from the Image
Self::from_image(device, queue, &image)
}
pub fn from_image(device: &wgpu::Device, queue: &mut wgpu::Queue, image: &image::DynamicImage) -> Result<Self, failure::Error> {
// Get data from image
let rgba = image.as_rgba8().unwrap();
let dimensions = image.dimensions();
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
depth: 1,
};
// Create a buffer on the GPU and load it with the image pixel data
let buffer = device.create_buffer_with_data(&rgba, wgpu::BufferUsage::COPY_SRC);
// Create an empty texture on the GPU of the correct size for the buffer
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size,
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
});
// Use a command encoder to transfer the pixel data buffer into the texture
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer: &buffer,
offset: 0,
bytes_per_row: 4 * dimensions.0,
rows_per_image: dimensions.1,
},
wgpu::TextureCopyView {
texture: &texture,
mip_level: 0,
array_layer: 0,
origin: wgpu::Origin3d::ZERO,
},
size,
);
// Finishing the encoding yields the resulting command buffer that is submitted to the GPU's command queue
let command_buffer = encoder.finish();
queue.submit(&[command_buffer]);
// Create the TextureView for this texture
let view = texture.create_default_view();
// Create the Sampler for this texture
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
lod_min_clamp: -100.0,
lod_max_clamp: 100.0,
compare: wgpu::CompareFunction::Always,
});
Ok(Self {
texture,
texture_view: view,
sampler,
})
}
}

View file

@ -1,105 +0,0 @@
use crate::layout_abstract_syntax::*;
use crate::layout_abstract_types::*;
use crate::{layout_system::*, resource_cache::ResourceCache};
use std::collections::HashMap;
pub struct WindowDom<'a> {
pub dom: rctree::Node<DomNode>,
loaded_components: &'a ResourceCache<FlatComponent>,
}
impl<'a> WindowDom<'a> {
pub fn new(root_component: &str, window_size: (u32, u32), loaded_components: &'a ResourceCache<FlatComponent>) -> WindowDom<'a> {
let mut layout_attributes = LayoutAttributes::default();
layout_attributes.width = Dimension::AbsolutePx(window_size.0 as f64);
layout_attributes.height = Dimension::AbsolutePx(window_size.1 as f64);
let dom = Self::build_dom_from_component(root_component, &layout_attributes, &vec![], loaded_components);
Self { dom, loaded_components }
}
fn build_dom_from_component(
root_component: &str,
layout_attributes: &LayoutAttributes,
props: &Vec<Prop>,
loaded_components: &'a ResourceCache<FlatComponent>,
) -> rctree::Node<DomNode> {
// Instantiate the DOM node and put it in a tree node
let component = loaded_components.get(root_component).unwrap();
let dom_node = DomNode::from_component(component, layout_attributes, props);
let mut tree = rctree::Node::new(dom_node);
// Recursively build the child `DomNode` tree node instances
let child_nodes = component
.child_components
.iter()
.map(|child| {
// Get the child name used as the component cache key
let (namespace, name) = &child.name;
let component_name = LayoutSystem::component_name((namespace, name));
// Recursively build the child `DomNode` component instance
Self::build_dom_from_component(&component_name[..], &child.layout, &child.props, loaded_components)
})
.collect::<Vec<_>>();
// Append each child `DomNode` tree node
for child in child_nodes {
tree.append(child);
}
// Return the tree that has been recursively built with sibling and child components
tree
}
}
pub struct DomNode {
pub cache_name: String,
pub layout_attributes: LayoutAttributes,
pub variable_bindings: HashMap<String, Vec<TypedValueOrVariableName>>,
}
impl DomNode {
pub fn new(cache_name: String, layout_attributes: LayoutAttributes, variable_bindings: HashMap<String, Vec<TypedValueOrVariableName>>) -> Self {
Self {
cache_name,
layout_attributes,
variable_bindings,
}
}
pub fn from_component(component: &FlatComponent, layout_attributes: &LayoutAttributes, props: &Vec<Prop>) -> Self {
// Cached name of the loaded component
let (namespace, name) = &component.own_info.name;
let cache_name = LayoutSystem::component_name((&namespace[..], &name[..]));
// Every VARIABLE_NAME binding defined in the prop definitions on this component
let mut variable_bindings = component
.own_info
.prop_definitions
.iter()
.map(|prop_definition| {
(
// HashMap key is the prop name
prop_definition.variable_name.clone(),
// HashMap value is the prop definition's default value
prop_definition
.type_sequence_default
.iter()
.map(|value| TypedValueOrVariableName::TypedValue(value.clone()))
.collect::<Vec<_>>(),
)
})
.collect::<HashMap<_, _>>();
// Overwrite the default values for the provided props
for prop in props {
if !variable_bindings.contains_key(&prop.name[..]) {
panic!("Invalid argument {} given to the {} component", prop.name, cache_name);
}
variable_bindings.insert(prop.name.clone(), prop.value_sequence.clone());
}
Self::new(cache_name, layout_attributes.clone(), variable_bindings)
}
}

View file

@ -1,70 +0,0 @@
use crate::application::Application;
use winit::event::*;
use winit::event_loop::ControlFlow;
pub fn window_event(application: &mut Application, control_flow: &mut ControlFlow, event: &WindowEvent) {
match event {
WindowEvent::Resized(physical_size) => resize(application, *physical_size),
WindowEvent::Moved(_) => (),
WindowEvent::CloseRequested => quit(control_flow),
WindowEvent::Destroyed => (),
WindowEvent::DroppedFile(_) => (),
WindowEvent::HoveredFile(_) => (),
WindowEvent::HoveredFileCancelled => (),
WindowEvent::ReceivedCharacter(_) => (),
WindowEvent::Focused(_) => (),
WindowEvent::KeyboardInput { input, .. } => keyboard_event(application, control_flow, input),
WindowEvent::CursorMoved { .. } => (),
WindowEvent::CursorEntered { .. } => (),
WindowEvent::CursorLeft { .. } => (),
WindowEvent::MouseWheel { .. } => (),
WindowEvent::MouseInput { .. } => (),
WindowEvent::TouchpadPressure { .. } => (),
WindowEvent::AxisMotion { .. } => (),
WindowEvent::Touch(_) => (),
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => resize(application, **new_inner_size),
WindowEvent::ThemeChanged(_) => (),
}
}
fn keyboard_event(application: &mut Application, control_flow: &mut ControlFlow, input: &KeyboardInput) {
match input {
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
} => quit(control_flow),
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Space),
..
} => {
// const VERTICES: &[[f32; 2]] = &[
// [-0.2, 0.0],
// [0.2, 0.0],
// [0.2, -0.5],
// [-0.2, -0.5],
// ];
// const INDICES: &[u16] = &[
// 0, 1, 2,
// 0, 2, 3,
// ];
// application.example(VERTICES, INDICES);
},
_ => *control_flow = ControlFlow::Wait,
}
}
fn quit(control_flow: &mut ControlFlow) {
*control_flow = ControlFlow::Exit;
}
fn resize(application: &mut Application, new_size: winit::dpi::PhysicalSize<u32>) {
application.swap_chain_descriptor.width = new_size.width;
application.swap_chain_descriptor.height = new_size.height;
application.swap_chain = application.device.create_swap_chain(&application.surface, &application.swap_chain_descriptor);
// TODO: Mark root of GUI as dirty to force redraw of everything
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB