Shelve GUI system, set up new Rust project structure
This commit is contained in:
parent
c72f8ba2db
commit
e21bca41c6
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
|
|
@ -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/*",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
260
gui/README.md
260
gui/README.md
|
|
@ -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]`*
|
||||
Doesn’t 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 node’s 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 can’t be transparent, parent can’t 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.
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<col children="INNER_XML: (Layout) = [[]]">
|
||||
{{INNER_XML}}
|
||||
</col>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<icon children="INNER_XML: (Layout) = [[]]" :svg="SVG_SOURCE: (TemplateString) = ``" :style="SVG_STYLE: (TemplateString) = ``">
|
||||
{{INNER_XML}}
|
||||
</icon>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<row children="INNER_XML: (Layout) = [[]]">
|
||||
{{INNER_XML}}
|
||||
</row>
|
||||
|
|
@ -1 +0,0 @@
|
|||
<text children="TEXT_STRING: (TemplateString) = `MISSING TEXT CONTENT`" :color="COLOR: (Color) = ['middlegray']" :size="SIZE: (AbsolutePx) = 12px"></text>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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]
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, Graphite!");
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
41
src/color.rs
41
src/color.rs
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(""", "\"")
|
||||
.replace("apos;", "'")
|
||||
.replace("'", "'")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
38
src/main.rs
38
src/main.rs
|
|
@ -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);
|
||||
}
|
||||
177
src/pipeline.rs
177
src/pipeline.rs
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
106
src/texture.rs
106
src/texture.rs
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 |
Loading…
Reference in New Issue