mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-19 06:40:20 +03:00
remove breakout example
This commit is contained in:
parent
b775c0e509
commit
3a38809360
@ -1,164 +0,0 @@
|
||||
app [program, Model] { pf: platform "platform/main.roc" }
|
||||
|
||||
import pf.Game exposing [Bounds, Elem, Event]
|
||||
|
||||
paddleWidth = 0.2 # width of the paddle, as a % of screen width
|
||||
paddleHeight = 50 # height of the paddle, in pixels
|
||||
paddleSpeed = 65 # how many pixels the paddle moves per keypress
|
||||
blockHeight = 80 # height of a block, in pixels
|
||||
blockBorder = 0.025 # border of a block, as a % of its width
|
||||
ballSize = 55
|
||||
numRows = 4
|
||||
numCols = 8
|
||||
numBlocks = numRows * numCols
|
||||
|
||||
Model : {
|
||||
# Screen height and width
|
||||
height : F32,
|
||||
width : F32,
|
||||
# Paddle X-coordinate
|
||||
paddleX : F32,
|
||||
# Ball coordinates
|
||||
ballX : F32,
|
||||
ballY : F32,
|
||||
dBallX : F32,
|
||||
# delta x - how much it moves per tick
|
||||
dBallY : F32,
|
||||
# delta y - how much it moves per tick
|
||||
}
|
||||
|
||||
init : Bounds -> Model
|
||||
init = \{ width, height } -> {
|
||||
# Screen height and width
|
||||
width,
|
||||
height,
|
||||
# Paddle X-coordinate
|
||||
paddleX: (width * 0.5) - (paddleWidth * width * 0.5),
|
||||
# Ball coordinates
|
||||
ballX: width * 0.5,
|
||||
ballY: height * 0.4,
|
||||
# Delta - how much ball moves in each tick
|
||||
dBallX: 4,
|
||||
dBallY: 4,
|
||||
}
|
||||
|
||||
update : Model, Event -> Model
|
||||
update = \model, event ->
|
||||
when event is
|
||||
Resize size ->
|
||||
{ model & width: size.width, height: size.height }
|
||||
|
||||
KeyDown Left ->
|
||||
{ model & paddleX: model.paddleX - paddleSpeed }
|
||||
|
||||
KeyDown Right ->
|
||||
{ model & paddleX: model.paddleX + paddleSpeed }
|
||||
|
||||
Tick _ ->
|
||||
tick model
|
||||
|
||||
_ ->
|
||||
model
|
||||
|
||||
tick : Model -> Model
|
||||
tick = \model ->
|
||||
model
|
||||
|> moveBall
|
||||
|
||||
moveBall : Model -> Model
|
||||
moveBall = \model ->
|
||||
ballX = model.ballX + model.dBallX
|
||||
ballY = model.ballY + model.dBallY
|
||||
|
||||
paddleTop = model.height - blockHeight - (paddleHeight * 2)
|
||||
paddleLeft = model.paddleX
|
||||
paddleRight = paddleLeft + (model.width * paddleWidth)
|
||||
|
||||
# If its y used to be less than the paddle, and now it's greater than or equal,
|
||||
# then this is the frame where the ball collided with it.
|
||||
crossingPaddle = model.ballY < paddleTop && ballY >= paddleTop
|
||||
|
||||
# If it collided with the paddle, bounce off.
|
||||
directionChange =
|
||||
if crossingPaddle && (ballX >= paddleLeft && ballX <= paddleRight) then
|
||||
-1f32
|
||||
else
|
||||
1f32
|
||||
|
||||
dBallX = model.dBallX * directionChange
|
||||
dBallY = model.dBallY * directionChange
|
||||
|
||||
{ model & ballX, ballY, dBallX, dBallY }
|
||||
|
||||
render : Model -> List Elem
|
||||
render = \model ->
|
||||
|
||||
blocks = List.map
|
||||
(List.range { start: At 0, end: Length numBlocks })
|
||||
\index ->
|
||||
col =
|
||||
Num.rem index numCols
|
||||
|> Num.toF32
|
||||
|
||||
row =
|
||||
index
|
||||
// numCols
|
||||
|> Num.toF32
|
||||
|
||||
red = col / Num.toF32 numCols
|
||||
green = row / Num.toF32 numRows
|
||||
blue = Num.toF32 index / Num.toF32 numBlocks
|
||||
|
||||
color = { r: red * 0.8, g: 0.2 + green * 0.6, b: 0.2 + blue * 0.8, a: 1 }
|
||||
|
||||
{ row, col, color }
|
||||
|
||||
blockWidth = model.width / numCols
|
||||
|
||||
rects =
|
||||
List.joinMap
|
||||
blocks
|
||||
\{ row, col, color } ->
|
||||
left = Num.toF32 col * blockWidth
|
||||
top = Num.toF32 (row * blockHeight)
|
||||
border = blockBorder * blockWidth
|
||||
|
||||
outer = Rect {
|
||||
left,
|
||||
top,
|
||||
width: blockWidth,
|
||||
height: blockHeight,
|
||||
color: { r: color.r * 0.8, g: color.g * 0.8, b: color.b * 0.8, a: 1 },
|
||||
}
|
||||
|
||||
inner = Rect {
|
||||
left: left + border,
|
||||
top: top + border,
|
||||
width: blockWidth - (border * 2),
|
||||
height: blockHeight - (border * 2),
|
||||
color,
|
||||
}
|
||||
|
||||
[outer, inner]
|
||||
|
||||
ball =
|
||||
color = { r: 0.7, g: 0.3, b: 0.9, a: 1.0 }
|
||||
width = ballSize
|
||||
height = ballSize
|
||||
left = model.ballX
|
||||
top = model.ballY
|
||||
|
||||
Rect { left, top, width, height, color }
|
||||
|
||||
paddle =
|
||||
color = { r: 0.8, g: 0.8, b: 0.8, a: 1.0 }
|
||||
width = model.width * paddleWidth
|
||||
height = paddleHeight
|
||||
left = model.paddleX
|
||||
top = model.height - blockHeight - height
|
||||
|
||||
Rect { left, top, width, height, color }
|
||||
|
||||
List.concat rects [paddle, ball]
|
||||
|
||||
program = { init, update, render }
|
@ -1,16 +0,0 @@
|
||||
app [program, Model] { pf: platform "platform/main.roc" }
|
||||
|
||||
import pf.Game exposing [Bounds, Elem, Event]
|
||||
|
||||
Model : { text : Str }
|
||||
|
||||
init : Bounds -> Model
|
||||
init = \_ -> { text: "Hello, World!" }
|
||||
|
||||
update : Model, Event -> Model
|
||||
update = \model, _ -> model
|
||||
|
||||
render : Model -> List Elem
|
||||
render = \model -> [Text { text: model.text, top: 0, left: 0, size: 40, color: { r: 1, g: 1, b: 1, a: 1 } }]
|
||||
|
||||
program = { init, update, render }
|
@ -1,15 +0,0 @@
|
||||
module [Action, none, update, map]
|
||||
|
||||
Action state : [None, Update state]
|
||||
|
||||
none : Action *
|
||||
none = None
|
||||
|
||||
update : state -> Action state
|
||||
update = Update
|
||||
|
||||
map : Action a, (a -> b) -> Action b
|
||||
map = \action, transform ->
|
||||
when action is
|
||||
None -> None
|
||||
Update state -> Update (transform state)
|
@ -1,57 +0,0 @@
|
||||
[package]
|
||||
name = "host"
|
||||
authors = ["The Roc Contributors"]
|
||||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[lib]
|
||||
name = "host"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["staticlib", "lib"]
|
||||
|
||||
[[bin]]
|
||||
name = "host"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.2"
|
||||
libc = "0.2"
|
||||
page_size = "0.4.2"
|
||||
roc_std = { path = "../../../../crates/roc_std" }
|
||||
cgmath = "0.18.0"
|
||||
colored = "2.0.0"
|
||||
copypasta = "0.7.1"
|
||||
fs_extra = "1.2.0"
|
||||
futures = "0.3.17"
|
||||
glyph_brush = "0.7.2"
|
||||
log = "0.4.14"
|
||||
nonempty = "0.7.0"
|
||||
palette = "0.6.0"
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
threadpool = "1.8.1"
|
||||
wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "0545e36" }
|
||||
wgpu_glyph = { git = "https://github.com/Anton-4/wgpu_glyph", rev = "257d109" }
|
||||
winit = "0.26.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies.bytemuck]
|
||||
version = "1.7.2"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace]
|
||||
|
||||
# Optimizations based on https://deterministic.space/high-performance-rust.html
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
# debug = true # enable when profiling
|
||||
[profile.bench]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
@ -1,192 +0,0 @@
|
||||
module [Elem, PressEvent, row, col, text, button, none, translate, list]
|
||||
|
||||
import Action exposing [Action]
|
||||
|
||||
Elem state : [
|
||||
# PERFORMANCE NOTE:
|
||||
# If there are 8 or fewer tags here, then on a 64-bit system, the tag can be stored
|
||||
# in the pointer - for massive memory savings. Try extremely hard to always limit the number
|
||||
# of tags in this union to 8 or fewer!
|
||||
Button (ButtonConfig state) (Elem state),
|
||||
Text Str,
|
||||
Col (List (Elem state)),
|
||||
Row (List (Elem state)),
|
||||
Lazy (Result { state, elem : Elem state } [NotCached] -> { state, elem : Elem state }),
|
||||
# TODO FIXME: using this definition of Lazy causes a stack overflow in the compiler!
|
||||
# Lazy (Result (Cached state) [NotCached] -> Cached state),
|
||||
None,
|
||||
]
|
||||
|
||||
## Used internally in the type definition of Lazy
|
||||
Cached state : { state, elem : Elem state }
|
||||
|
||||
ButtonConfig state : { onPress : state, PressEvent -> Action state }
|
||||
|
||||
PressEvent : { button : [Touch, Mouse [Left, Right, Middle]] }
|
||||
|
||||
text : Str -> Elem *
|
||||
text = \str ->
|
||||
Text str
|
||||
|
||||
button : { onPress : state, PressEvent -> Action state }, Elem state -> Elem state
|
||||
button = \config, label ->
|
||||
Button config label
|
||||
|
||||
row : List (Elem state) -> Elem state
|
||||
row = \children ->
|
||||
Row children
|
||||
|
||||
col : List (Elem state) -> Elem state
|
||||
col = \children ->
|
||||
Col children
|
||||
|
||||
lazy : state, (state -> Elem state) -> Elem state
|
||||
lazy = \state, render ->
|
||||
# This function gets called by the host during rendering. It will
|
||||
# receive the cached state and element (wrapped in Ok) if we've
|
||||
# ever rendered this before, and Err otherwise.
|
||||
Lazy
|
||||
\result ->
|
||||
when result is
|
||||
Ok cached if cached.state == state ->
|
||||
# If we have a cached value, and the new state is the
|
||||
# same as the cached one, then we can return exactly
|
||||
# what we had cached.
|
||||
cached
|
||||
|
||||
_ ->
|
||||
# Either the state changed or else we didn't have a
|
||||
# cached value to use. Either way, we need to render
|
||||
# with the new state and store that for future use.
|
||||
{ state, elem: render state }
|
||||
|
||||
none : Elem *
|
||||
none = None # I've often wanted this in elm/html. Usually end up resorting to (Html.text "") - this seems nicer.
|
||||
## Change an element's state type.
|
||||
##
|
||||
## TODO: indent the following once https://github.com/roc-lang/roc/issues/2585 is fixed.
|
||||
## State : { photo : Photo }
|
||||
##
|
||||
## render : State -> Elem State
|
||||
## render = \state ->
|
||||
## child : Elem State
|
||||
## child =
|
||||
## Photo.render state.photo
|
||||
## |> Elem.translate .photo &photo
|
||||
##
|
||||
## col {} [child, otherElems]
|
||||
##
|
||||
translate = \child, toChild, toParent ->
|
||||
when child is
|
||||
Text str ->
|
||||
Text str
|
||||
|
||||
Col elems ->
|
||||
Col (List.map elems \elem -> translate elem toChild toParent)
|
||||
|
||||
Row elems ->
|
||||
Row (List.map elems \elem -> translate elem toChild toParent)
|
||||
|
||||
Button config label ->
|
||||
onPress = \parentState, event ->
|
||||
toChild parentState
|
||||
|> config.onPress event
|
||||
|> Action.map \c -> toParent parentState c
|
||||
|
||||
Button { onPress } (translate label toChild toParent)
|
||||
|
||||
Lazy renderChild ->
|
||||
Lazy
|
||||
\parentState ->
|
||||
{ elem, state } = renderChild (toChild parentState)
|
||||
|
||||
{
|
||||
elem: translate toChild toParent newChild,
|
||||
state: toParent parentState state,
|
||||
}
|
||||
|
||||
None ->
|
||||
None
|
||||
|
||||
## Render a list of elements, using [Elem.translate] on each of them.
|
||||
##
|
||||
## Convenient when you have a [List] in your state and want to make
|
||||
## a [List] of child elements out of it.
|
||||
##
|
||||
## TODO: indent the following once https://github.com/roc-lang/roc/issues/2585 is fixed.
|
||||
## State : { photos : List Photo }
|
||||
##
|
||||
## render : State -> Elem State
|
||||
## render = \state ->
|
||||
## children : List (Elem State)
|
||||
## children =
|
||||
## Elem.list Photo.render state .photos &photos
|
||||
##
|
||||
## col {} children
|
||||
## TODO: format as multiline type annotation once https://github.com/roc-lang/roc/issues/2586 is fixed
|
||||
list : (child -> Elem child), parent, (parent -> List child), (parent, List child -> parent) -> List (Elem parent)
|
||||
list = \renderChild, parent, toChildren, toParent ->
|
||||
List.mapWithIndex
|
||||
(toChildren parent)
|
||||
\index, child ->
|
||||
toChild = \par -> List.get (toChildren par) index
|
||||
|
||||
newChild = translateOrDrop
|
||||
child
|
||||
toChild
|
||||
\par, ch ->
|
||||
toChildren par
|
||||
|> List.set ch index
|
||||
|> toParent
|
||||
|
||||
renderChild newChild
|
||||
|
||||
## Internal helper function for Elem.list
|
||||
##
|
||||
## Tries to translate a child to a parent, but
|
||||
## if the child has been removed from the parent,
|
||||
## drops it.
|
||||
##
|
||||
## TODO: format as multiline type annotation once https://github.com/roc-lang/roc/issues/2586 is fixed
|
||||
translateOrDrop : Elem child, (parent -> Result child *), (parent, child -> parent) -> Elem parent
|
||||
translateOrDrop = \child, toChild, toParent ->
|
||||
when child is
|
||||
Text str ->
|
||||
Text str
|
||||
|
||||
Col elems ->
|
||||
Col (List.map elems \elem -> translateOrDrop elem toChild toParent)
|
||||
|
||||
Row elems ->
|
||||
Row (List.map elems \elem -> translateOrDrop elem toChild toParent)
|
||||
|
||||
Button config label ->
|
||||
onPress = \parentState, event ->
|
||||
when toChild parentState is
|
||||
Ok newChild ->
|
||||
newChild
|
||||
|> config.onPress event
|
||||
|> Action.map \c -> toParent parentState c
|
||||
|
||||
Err _ ->
|
||||
# The child was removed from the list before this onPress handler resolved.
|
||||
# (For example, by a previous event handler that fired simultaneously.)
|
||||
Action.none
|
||||
|
||||
Button { onPress } (translateOrDrop label toChild toParent)
|
||||
|
||||
Lazy childState renderChild ->
|
||||
Lazy
|
||||
(toParent childState)
|
||||
\parentState ->
|
||||
when toChild parentState is
|
||||
Ok newChild ->
|
||||
renderChild newChild
|
||||
|> translateOrDrop toChild toParent
|
||||
|
||||
Err _ ->
|
||||
None
|
||||
|
||||
# I don't think this should ever happen in practice.
|
||||
None ->
|
||||
None
|
@ -1,11 +0,0 @@
|
||||
module [Bounds, Elem, Event]
|
||||
|
||||
Rgba : { r : F32, g : F32, b : F32, a : F32 }
|
||||
|
||||
Bounds : { height : F32, width : F32 }
|
||||
|
||||
Elem : [Rect { color : Rgba, left : F32, top : F32, width : F32, height : F32 }, Text { text : Str, color : Rgba, left : F32, top : F32, size : F32 }]
|
||||
|
||||
KeyCode : [Left, Right, Other, Up, Down]
|
||||
|
||||
Event : [Resize { width : F32, height : F32 }, KeyDown KeyCode, KeyUp KeyCode, Tick U128]
|
@ -1,9 +0,0 @@
|
||||
fn main() {
|
||||
#[cfg(not(windows))]
|
||||
println!("cargo:rustc-link-lib=dylib=app");
|
||||
|
||||
#[cfg(windows)]
|
||||
println!("cargo:rustc-link-lib=dylib=libapp");
|
||||
|
||||
println!("cargo:rustc-link-search=.");
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
extern int rust_main();
|
||||
|
||||
int main() { return rust_main(); }
|
@ -1,14 +0,0 @@
|
||||
platform "gui"
|
||||
requires { Model } { program : _ }
|
||||
exposes [Game]
|
||||
packages {}
|
||||
imports [Game.{ Bounds, Elem, Event }]
|
||||
provides [programForHost]
|
||||
|
||||
# TODO allow changing the window title - maybe via a Task, since that shouldn't happen all the time
|
||||
programForHost : {
|
||||
init : (Bounds -> Model) as Init,
|
||||
update : (Model, Event -> Model) as Update,
|
||||
render : (Model -> List Elem) as Render,
|
||||
}
|
||||
programForHost = program
|
@ -1,50 +0,0 @@
|
||||
use cgmath::Vector4;
|
||||
use palette::{FromColor, Hsv, Srgb};
|
||||
|
||||
/// This order is optimized for what Roc will send
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
pub struct Rgba {
|
||||
a: f32,
|
||||
b: f32,
|
||||
g: f32,
|
||||
r: f32,
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
|
||||
pub const fn to_array(self) -> [f32; 4] {
|
||||
[self.r, self.g, self.b, self.a]
|
||||
}
|
||||
|
||||
pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> Self {
|
||||
Self::from_hsba(hue, saturation, brightness, 1.0)
|
||||
}
|
||||
|
||||
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> Self {
|
||||
let rgb = Srgb::from_color(Hsv::new(
|
||||
hue as f32,
|
||||
(saturation as f32) / 100.0,
|
||||
(brightness as f32) / 100.0,
|
||||
));
|
||||
|
||||
Self::new(rgb.red, rgb.green, rgb.blue, alpha)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for [f32; 4] {
|
||||
fn from(rgba: Rgba) -> Self {
|
||||
rgba.to_array()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for Vector4<f32> {
|
||||
fn from(rgba: Rgba) -> Self {
|
||||
Vector4::new(rgba.r, rgba.b, rgba.g, rgba.a)
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
// Contains parts of https://github.com/sotrh/learn-wgpu
|
||||
// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS
|
||||
// file in the root directory of this distribution.
|
||||
//
|
||||
// Thank you, Benjamin!
|
||||
|
||||
// Contains parts of https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl
|
||||
// By Héctor Ramón, Iced contributors Licensed under the MIT license.
|
||||
// The license is included in the LEGAL_DETAILS file in the root directory of this distribution.
|
||||
|
||||
// Thank you Héctor Ramón and Iced contributors!
|
||||
|
||||
use std::mem;
|
||||
|
||||
use super::{quad::Quad, vertex::Vertex};
|
||||
use crate::graphics::primitives::rect::RectElt;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
pub struct RectBuffers {
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub quad_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
pub const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
||||
|
||||
const QUAD_VERTS: [Vertex; 4] = [
|
||||
Vertex {
|
||||
_position: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
_position: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
_position: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
_position: [0.0, 1.0],
|
||||
},
|
||||
];
|
||||
|
||||
pub const MAX_QUADS: usize = 1_000;
|
||||
|
||||
pub fn create_rect_buffers(
|
||||
gpu_device: &wgpu::Device,
|
||||
cmd_encoder: &mut wgpu::CommandEncoder,
|
||||
rects: &[RectElt],
|
||||
) -> RectBuffers {
|
||||
let vertex_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&QUAD_VERTS),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let index_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&QUAD_INDICES),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
let quad_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: mem::size_of::<Quad>() as u64 * MAX_QUADS as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let quads: Vec<Quad> = rects.iter().map(|rect| to_quad(rect)).collect();
|
||||
|
||||
let buffer_size = (quads.len() as u64) * Quad::SIZE;
|
||||
|
||||
let staging_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&quads),
|
||||
usage: wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
|
||||
cmd_encoder.copy_buffer_to_buffer(&staging_buffer, 0, &quad_buffer, 0, buffer_size);
|
||||
|
||||
RectBuffers {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
quad_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_quad(rect_elt: &RectElt) -> Quad {
|
||||
Quad {
|
||||
pos: rect_elt.rect.pos.into(),
|
||||
width: rect_elt.rect.width,
|
||||
height: rect_elt.rect.height,
|
||||
color: (rect_elt.color.to_array()),
|
||||
border_color: rect_elt.border_color.into(),
|
||||
border_width: rect_elt.border_width,
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
pub mod buffer;
|
||||
pub mod ortho;
|
||||
pub mod pipelines;
|
||||
pub mod vertex;
|
||||
pub mod quad;
|
@ -1,118 +0,0 @@
|
||||
use cgmath::{Matrix4, Ortho};
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu::{
|
||||
BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer,
|
||||
ShaderStages,
|
||||
};
|
||||
|
||||
// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Uniforms {
|
||||
// We can't use cgmath with bytemuck directly so we'll have
|
||||
// to convert the Matrix4 into a 4x4 f32 array
|
||||
ortho: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new(w: u32, h: u32) -> Self {
|
||||
let ortho: Matrix4<f32> = Ortho::<f32> {
|
||||
left: 0.0,
|
||||
right: w as f32,
|
||||
bottom: h as f32,
|
||||
top: 0.0,
|
||||
near: -1.0,
|
||||
far: 1.0,
|
||||
}
|
||||
.into();
|
||||
Self {
|
||||
ortho: ortho.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update orthographic buffer according to new window size
|
||||
pub fn update_ortho_buffer(
|
||||
inner_width: u32,
|
||||
inner_height: u32,
|
||||
gpu_device: &wgpu::Device,
|
||||
ortho_buffer: &Buffer,
|
||||
cmd_queue: &wgpu::Queue,
|
||||
) {
|
||||
let new_uniforms = Uniforms::new(inner_width, inner_height);
|
||||
|
||||
let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Ortho uniform buffer"),
|
||||
contents: bytemuck::cast_slice(&[new_uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
|
||||
// get a command encoder for the current frame
|
||||
let mut encoder = gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Resize"),
|
||||
});
|
||||
|
||||
// overwrite the new buffer over the old one
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&new_ortho_buffer,
|
||||
0,
|
||||
ortho_buffer,
|
||||
0,
|
||||
(std::mem::size_of::<Uniforms>() * vec![new_uniforms].as_slice().len())
|
||||
as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
cmd_queue.submit(Some(encoder.finish()));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OrthoResources {
|
||||
pub buffer: Buffer,
|
||||
pub bind_group_layout: BindGroupLayout,
|
||||
pub bind_group: BindGroup,
|
||||
}
|
||||
|
||||
pub fn init_ortho(
|
||||
inner_width: u32,
|
||||
inner_height: u32,
|
||||
gpu_device: &wgpu::Device,
|
||||
) -> OrthoResources {
|
||||
let uniforms = Uniforms::new(inner_width, inner_height);
|
||||
|
||||
let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Ortho uniform buffer"),
|
||||
contents: bytemuck::cast_slice(&[uniforms]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
// bind groups consist of extra resources that are provided to the shaders
|
||||
let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||
entries: &[BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("Ortho bind group layout"),
|
||||
});
|
||||
|
||||
let ortho_bind_group = gpu_device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &ortho_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: ortho_buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("Ortho bind group"),
|
||||
});
|
||||
|
||||
OrthoResources {
|
||||
buffer: ortho_buffer,
|
||||
bind_group_layout: ortho_bind_group_layout,
|
||||
bind_group: ortho_bind_group,
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
use super::ortho::{init_ortho, OrthoResources};
|
||||
use super::quad::Quad;
|
||||
use super::vertex::Vertex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct RectResources {
|
||||
pub pipeline: wgpu::RenderPipeline,
|
||||
pub ortho: OrthoResources,
|
||||
}
|
||||
|
||||
pub fn make_rect_pipeline(
|
||||
gpu_device: &wgpu::Device,
|
||||
surface_config: &wgpu::SurfaceConfiguration,
|
||||
) -> RectResources {
|
||||
let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device);
|
||||
|
||||
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&ortho.bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let pipeline = create_render_pipeline(
|
||||
gpu_device,
|
||||
&pipeline_layout,
|
||||
surface_config.format,
|
||||
&wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/quad.wgsl"))),
|
||||
},
|
||||
);
|
||||
|
||||
RectResources { pipeline, ortho }
|
||||
}
|
||||
|
||||
pub fn create_render_pipeline(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
color_format: wgpu::TextureFormat,
|
||||
shader_module_desc: &wgpu::ShaderModuleDescriptor,
|
||||
) -> wgpu::RenderPipeline {
|
||||
let shader = device.create_shader_module(shader_module_desc);
|
||||
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Render pipeline"),
|
||||
layout: Some(layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[Vertex::DESC, Quad::DESC],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[wgpu::ColorTargetState {
|
||||
format: color_format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
src_factor: wgpu::BlendFactor::SrcAlpha,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
},
|
||||
alpha: wgpu::BlendComponent::REPLACE,
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
}],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
|
||||
|
||||
/// A polygon with 4 corners
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Quad {
|
||||
pub pos: [f32; 2],
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub color: [f32; 4],
|
||||
pub border_color: [f32; 4],
|
||||
pub border_width: f32,
|
||||
}
|
||||
|
||||
// Safety: Pod's contract says the type must
|
||||
// not have any padding, and must be repr(C).
|
||||
// As currrently defined, Quad does not have
|
||||
// any padding.
|
||||
unsafe impl bytemuck::Pod for Quad {}
|
||||
unsafe impl bytemuck::Zeroable for Quad {}
|
||||
|
||||
impl Quad {
|
||||
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &wgpu::vertex_attr_array!(
|
||||
1 => Float32x2,
|
||||
2 => Float32,
|
||||
3 => Float32,
|
||||
4 => Float32x4,
|
||||
5 => Float32x4,
|
||||
6 => Float32,
|
||||
),
|
||||
};
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// Inspired by https://github.com/sotrh/learn-wgpu
|
||||
// by Benjamin Hansen - license information can be found in the LEGAL_DETAILS
|
||||
// file in the root directory of this distribution.
|
||||
//
|
||||
// Thank you, Benjamin!
|
||||
|
||||
// Inspired by https://github.com/iced-rs/iced/blob/adce9e04213803bd775538efddf6e7908d1c605e/wgpu/src/shader/quad.wgsl
|
||||
// By Héctor Ramón, Iced contributors Licensed under the MIT license.
|
||||
// The license is included in the LEGAL_DETAILS file in the root directory of this distribution.
|
||||
|
||||
// Thank you Héctor Ramón and Iced contributors!
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Zeroable, Pod)]
|
||||
pub struct Vertex {
|
||||
pub _position: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
// position
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub mod colors;
|
||||
pub mod lowlevel;
|
||||
pub mod primitives;
|
||||
pub mod style;
|
@ -1,2 +0,0 @@
|
||||
pub mod rect;
|
||||
pub mod text;
|
@ -1,27 +0,0 @@
|
||||
use crate::graphics::colors::Rgba;
|
||||
use cgmath::Vector2;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RectElt {
|
||||
pub rect: Rect,
|
||||
pub color: Rgba,
|
||||
pub border_width: f32,
|
||||
pub border_color: Rgba,
|
||||
}
|
||||
|
||||
/// These fields are ordered this way because in Roc, the corresponding stuct is:
|
||||
///
|
||||
/// { top : F32, left : F32, width : F32, height : F32 }
|
||||
///
|
||||
/// alphabetically, that's { height, left, top, width } - which works out to the same as:
|
||||
///
|
||||
/// struct Rect { height: f32, pos: Vector2<f32>, width: f32 }
|
||||
///
|
||||
/// ...because Vector2<f32> is a repr(C) struct of { x: f32, y: f32 }
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct Rect {
|
||||
pub height: f32,
|
||||
pub pos: Vector2<f32>,
|
||||
pub width: f32,
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
// Adapted from https://github.com/sotrh/learn-wgpu
|
||||
// by Benjamin Hansen - license information can be found in the COPYRIGHT
|
||||
// file in the root directory of this distribution.
|
||||
//
|
||||
// Thank you, Benjamin!
|
||||
|
||||
use crate::graphics::colors::Rgba;
|
||||
use crate::graphics::style::DEFAULT_FONT_SIZE;
|
||||
use ab_glyph::{FontArc, InvalidFont};
|
||||
use cgmath::Vector2;
|
||||
use wgpu_glyph::{ab_glyph, GlyphBrush, GlyphBrushBuilder};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Text<'a> {
|
||||
pub position: Vector2<f32>,
|
||||
pub area_bounds: Vector2<f32>,
|
||||
pub color: Rgba,
|
||||
pub text: &'a str,
|
||||
pub size: f32,
|
||||
pub visible: bool,
|
||||
pub centered: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for Text<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: (0.0, 0.0).into(),
|
||||
area_bounds: (std::f32::INFINITY, std::f32::INFINITY).into(),
|
||||
color: Rgba::WHITE,
|
||||
text: "",
|
||||
size: DEFAULT_FONT_SIZE,
|
||||
visible: true,
|
||||
centered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn layout_from_text(text: &Text) -> wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker> {
|
||||
// wgpu_glyph::Layout::default().h_align(if text.centered {
|
||||
// wgpu_glyph::HorizontalAlign::Center
|
||||
// } else {
|
||||
// wgpu_glyph::HorizontalAlign::Left
|
||||
// })
|
||||
// }
|
||||
|
||||
// fn section_from_text<'a>(
|
||||
// text: &'a Text,
|
||||
// layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
// ) -> wgpu_glyph::Section<'a> {
|
||||
// Section {
|
||||
// screen_position: text.position.into(),
|
||||
// bounds: text.area_bounds.into(),
|
||||
// layout,
|
||||
// ..Section::default()
|
||||
// }
|
||||
// .add_text(
|
||||
// wgpu_glyph::Text::new(text.text)
|
||||
// .with_color(text.color)
|
||||
// .with_scale(text.size),
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub fn owned_section_from_text(text: &Text) -> OwnedSection {
|
||||
// let layout = layout_from_text(text);
|
||||
|
||||
// OwnedSection {
|
||||
// screen_position: text.position.into(),
|
||||
// bounds: text.area_bounds.into(),
|
||||
// layout,
|
||||
// ..OwnedSection::default()
|
||||
// }
|
||||
// .add_text(
|
||||
// glyph_brush::OwnedText::new(text.text)
|
||||
// .with_color(Vector4::from(text.color))
|
||||
// .with_scale(text.size),
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub fn owned_section_from_glyph_texts(
|
||||
// text: Vec<glyph_brush::OwnedText>,
|
||||
// screen_position: (f32, f32),
|
||||
// area_bounds: (f32, f32),
|
||||
// layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
// ) -> glyph_brush::OwnedSection {
|
||||
// glyph_brush::OwnedSection {
|
||||
// screen_position,
|
||||
// bounds: area_bounds,
|
||||
// layout,
|
||||
// text,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn queue_text_draw(text: &Text, glyph_brush: &mut GlyphBrush<()>) {
|
||||
// let layout = layout_from_text(text);
|
||||
|
||||
// let section = section_from_text(text, layout);
|
||||
|
||||
// glyph_brush.queue(section.clone());
|
||||
// }
|
||||
|
||||
// fn glyph_to_rect(glyph: &wgpu_glyph::SectionGlyph) -> Rect {
|
||||
// let position = glyph.glyph.position;
|
||||
// let px_scale = glyph.glyph.scale;
|
||||
// let width = glyph_width(&glyph.glyph);
|
||||
// let height = px_scale.y;
|
||||
// let top_y = glyph_top_y(&glyph.glyph);
|
||||
|
||||
// Rect {
|
||||
// pos: [position.x, top_y].into(),
|
||||
// width,
|
||||
// height,
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn glyph_top_y(glyph: &Glyph) -> f32 {
|
||||
// let height = glyph.scale.y;
|
||||
|
||||
// glyph.position.y - height * 0.75
|
||||
// }
|
||||
|
||||
// pub fn glyph_width(glyph: &Glyph) -> f32 {
|
||||
// glyph.scale.x * 0.4765
|
||||
// }
|
||||
|
||||
pub fn build_glyph_brush(
|
||||
gpu_device: &wgpu::Device,
|
||||
render_format: wgpu::TextureFormat,
|
||||
) -> Result<GlyphBrush<()>, InvalidFont> {
|
||||
let inconsolata = FontArc::try_from_slice(include_bytes!(
|
||||
"../../../../../Inconsolata-Regular.ttf"
|
||||
))?;
|
||||
|
||||
Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format))
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
|
||||
|
||||
struct Globals {
|
||||
ortho: mat4x4<f32>;
|
||||
};
|
||||
|
||||
@group(0)
|
||||
@binding(0)
|
||||
var<uniform> globals: Globals;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>;
|
||||
};
|
||||
|
||||
struct Quad {
|
||||
@location(1) pos: vec2<f32>; // can't use the name "position" twice for compatibility with metal on MacOS
|
||||
@location(2) width: f32;
|
||||
@location(3) height: f32;
|
||||
@location(4) color: vec4<f32>;
|
||||
@location(5) border_color: vec4<f32>;
|
||||
@location(6) border_width: f32;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>;
|
||||
@location(0) color: vec4<f32>;
|
||||
@location(1) border_color: vec4<f32>;
|
||||
@location(2) border_width: f32;
|
||||
};
|
||||
|
||||
@stage(vertex)
|
||||
fn vs_main(
|
||||
input: VertexInput,
|
||||
quad: Quad
|
||||
) -> VertexOutput {
|
||||
|
||||
var transform: mat4x4<f32> = mat4x4<f32>(
|
||||
vec4<f32>(quad.width, 0.0, 0.0, 0.0),
|
||||
vec4<f32>(0.0, quad.height, 0.0, 0.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
||||
vec4<f32>(quad.pos, 0.0, 1.0)
|
||||
);
|
||||
|
||||
var out: VertexOutput;
|
||||
|
||||
out.position = globals.ortho * transform * vec4<f32>(input.position, 0.0, 1.0);;
|
||||
out.color = quad.color;
|
||||
out.border_color = quad.border_color;
|
||||
out.border_width = quad.border_width;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@stage(fragment)
|
||||
fn fs_main(
|
||||
input: VertexOutput
|
||||
) -> @location(0) vec4<f32> {
|
||||
return input.color;
|
||||
}
|
@ -1 +0,0 @@
|
||||
pub const DEFAULT_FONT_SIZE: f32 = 30.0;
|
@ -1,513 +0,0 @@
|
||||
use crate::{
|
||||
graphics::{
|
||||
colors::Rgba,
|
||||
lowlevel::buffer::create_rect_buffers,
|
||||
lowlevel::{buffer::MAX_QUADS, ortho::update_ortho_buffer},
|
||||
lowlevel::{buffer::QUAD_INDICES, pipelines},
|
||||
primitives::{
|
||||
rect::{Rect, RectElt},
|
||||
text::build_glyph_brush,
|
||||
},
|
||||
},
|
||||
roc::{self, Bounds, RocElem, RocElemTag, RocEvent},
|
||||
};
|
||||
use cgmath::{Vector2, Vector4};
|
||||
use glyph_brush::{GlyphCruncher, OwnedSection};
|
||||
use pipelines::RectResources;
|
||||
use std::{
|
||||
error::Error,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use wgpu::{CommandEncoder, LoadOp, RenderPass, TextureView};
|
||||
use wgpu_glyph::GlyphBrush;
|
||||
use winit::{
|
||||
dpi::PhysicalSize,
|
||||
event,
|
||||
event::{ElementState, Event, ModifiersState, StartCause},
|
||||
event_loop::ControlFlow,
|
||||
platform::run_return::EventLoopExtRunReturn,
|
||||
};
|
||||
|
||||
// Inspired by:
|
||||
// https://github.com/sotrh/learn-wgpu by Benjamin Hansen, which is licensed under the MIT license
|
||||
// https://github.com/cloudhead/rgx by Alexis Sellier, which is licensed under the MIT license
|
||||
//
|
||||
// See this link to learn wgpu: https://sotrh.github.io/learn-wgpu/
|
||||
|
||||
const TIME_BETWEEN_TICKS: Duration = Duration::new(0, 1000 / 60);
|
||||
|
||||
pub fn run_event_loop(title: &str, window_bounds: Bounds) -> Result<(), Box<dyn Error>> {
|
||||
let (mut model, mut elems) = roc::init_and_render(window_bounds);
|
||||
|
||||
// Open window and create a surface
|
||||
let mut event_loop = winit::event_loop::EventLoop::new();
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_inner_size(PhysicalSize::new(window_bounds.width, window_bounds.height))
|
||||
.with_title(title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
macro_rules! update_and_rerender {
|
||||
($event:expr) => {
|
||||
// TODO use (model, elems) = ... once we've upgraded rust versions
|
||||
let pair = roc::update_and_render(model, $event);
|
||||
|
||||
model = pair.0;
|
||||
elems = pair.1;
|
||||
|
||||
window.request_redraw();
|
||||
};
|
||||
}
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
// Initialize GPU
|
||||
let (gpu_device, cmd_queue) = futures::executor::block_on(async {
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.expect(r#"Request adapter
|
||||
If you're running this from inside nix, run with:
|
||||
`nixVulkanIntel <your previous command that generated this error>`.
|
||||
See extra docs here: github.com/guibou/nixGL
|
||||
"#);
|
||||
|
||||
adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::default(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Request device")
|
||||
});
|
||||
|
||||
// Create staging belt and a local pool
|
||||
let mut staging_belt = wgpu::util::StagingBelt::new(1024);
|
||||
let mut local_pool = futures::executor::LocalPool::new();
|
||||
let local_spawner = local_pool.spawner();
|
||||
|
||||
// Prepare swap chain
|
||||
let render_format = wgpu::TextureFormat::Bgra8Unorm;
|
||||
let mut size = window.inner_size();
|
||||
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: render_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Mailbox,
|
||||
};
|
||||
|
||||
surface.configure(&gpu_device, &surface_config);
|
||||
|
||||
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config);
|
||||
|
||||
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
|
||||
let mut keyboard_modifiers = ModifiersState::empty();
|
||||
|
||||
// Render loop
|
||||
let app_start_time = Instant::now();
|
||||
let mut next_tick = app_start_time + TIME_BETWEEN_TICKS;
|
||||
|
||||
window.request_redraw();
|
||||
|
||||
event_loop.run_return(|event, _, control_flow| {
|
||||
match event {
|
||||
// Close
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
// Resize
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::Resized(new_size),
|
||||
..
|
||||
} => {
|
||||
size = new_size;
|
||||
|
||||
surface.configure(
|
||||
&gpu_device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: render_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Mailbox,
|
||||
},
|
||||
);
|
||||
|
||||
update_ortho_buffer(
|
||||
size.width,
|
||||
size.height,
|
||||
&gpu_device,
|
||||
&rect_resources.ortho.buffer,
|
||||
&cmd_queue,
|
||||
);
|
||||
|
||||
update_and_rerender!(RocEvent::Resize(Bounds {
|
||||
height: size.height as f32,
|
||||
width: size.width as f32,
|
||||
}));
|
||||
}
|
||||
// Keyboard input
|
||||
Event::WindowEvent {
|
||||
event:
|
||||
event::WindowEvent::KeyboardInput {
|
||||
input:
|
||||
event::KeyboardInput {
|
||||
virtual_keycode: Some(keycode),
|
||||
state: input_state,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let roc_event = match input_state {
|
||||
ElementState::Pressed => RocEvent::KeyDown(keycode.into()),
|
||||
ElementState::Released => RocEvent::KeyUp(keycode.into()),
|
||||
};
|
||||
|
||||
model = roc::update(model, roc_event);
|
||||
}
|
||||
// Modifiers Changed
|
||||
Event::WindowEvent {
|
||||
event: event::WindowEvent::ModifiersChanged(modifiers),
|
||||
..
|
||||
} => {
|
||||
keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::RedrawRequested { .. } => {
|
||||
// Get a command cmd_encoder for the current frame
|
||||
let mut cmd_encoder =
|
||||
gpu_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Redraw"),
|
||||
});
|
||||
|
||||
let surface_texture = surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next SwapChainTexture");
|
||||
|
||||
let view = surface_texture
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
for elem in elems.iter() {
|
||||
let (_bounds, drawable) = to_drawable(
|
||||
elem,
|
||||
Bounds {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
},
|
||||
&mut glyph_brush,
|
||||
);
|
||||
|
||||
process_drawable(
|
||||
drawable,
|
||||
&mut staging_belt,
|
||||
&mut glyph_brush,
|
||||
&mut cmd_encoder,
|
||||
&view,
|
||||
&gpu_device,
|
||||
&rect_resources,
|
||||
wgpu::LoadOp::Load,
|
||||
Bounds {
|
||||
width: size.width as f32,
|
||||
height: size.height as f32,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
staging_belt.finish();
|
||||
cmd_queue.submit(Some(cmd_encoder.finish()));
|
||||
surface_texture.present();
|
||||
|
||||
// Recall unused staging buffers
|
||||
use futures::task::SpawnExt;
|
||||
|
||||
local_spawner
|
||||
.spawn(staging_belt.recall())
|
||||
.expect("Recall staging belt");
|
||||
|
||||
local_pool.run_until_stalled();
|
||||
}
|
||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
||||
requested_resume, ..
|
||||
}) => {
|
||||
// Only run this logic if this is the tick we originally requested.
|
||||
if requested_resume == next_tick {
|
||||
let now = Instant::now();
|
||||
|
||||
// Set a new next_tick *before* running update and rerender,
|
||||
// so their runtime isn't factored into when we want to render next.
|
||||
next_tick = now + TIME_BETWEEN_TICKS;
|
||||
|
||||
let tick = now.saturating_duration_since(app_start_time);
|
||||
|
||||
update_and_rerender!(RocEvent::Tick(tick));
|
||||
|
||||
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Keep waiting until the next tick.
|
||||
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_tick);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_rects(
|
||||
all_rects: &[RectElt],
|
||||
cmd_encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
) {
|
||||
let rect_buffers = create_rect_buffers(gpu_device, cmd_encoder, all_rects);
|
||||
|
||||
let mut render_pass = begin_render_pass(cmd_encoder, texture_view, load_op);
|
||||
|
||||
render_pass.set_pipeline(&rect_resources.pipeline);
|
||||
render_pass.set_bind_group(0, &rect_resources.ortho.bind_group, &[]);
|
||||
|
||||
render_pass.set_vertex_buffer(0, rect_buffers.vertex_buffer.slice(..));
|
||||
render_pass.set_vertex_buffer(1, rect_buffers.quad_buffer.slice(..));
|
||||
|
||||
render_pass.set_index_buffer(
|
||||
rect_buffers.index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint16,
|
||||
);
|
||||
|
||||
render_pass.draw_indexed(0..QUAD_INDICES.len() as u32, 0, 0..MAX_QUADS as u32);
|
||||
}
|
||||
|
||||
fn begin_render_pass<'a>(
|
||||
cmd_encoder: &'a mut CommandEncoder,
|
||||
texture_view: &'a TextureView,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
) -> RenderPass<'a> {
|
||||
cmd_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachment {
|
||||
view: texture_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: load_op,
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
label: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Drawable {
|
||||
pos: Vector2<f32>,
|
||||
bounds: Bounds,
|
||||
content: DrawableContent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum DrawableContent {
|
||||
/// This stores an actual Section because an earlier step needs to know the bounds of
|
||||
/// the text, and making a Section is a convenient way to compute those bounds.
|
||||
Text(OwnedSection, Vector2<f32>),
|
||||
FillRect {
|
||||
color: Rgba,
|
||||
border_width: f32,
|
||||
border_color: Rgba,
|
||||
},
|
||||
}
|
||||
|
||||
fn process_drawable(
|
||||
drawable: Drawable,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
cmd_encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
texture_size: Bounds,
|
||||
) {
|
||||
draw(
|
||||
drawable.bounds,
|
||||
drawable.content,
|
||||
drawable.pos,
|
||||
staging_belt,
|
||||
glyph_brush,
|
||||
cmd_encoder,
|
||||
texture_view,
|
||||
gpu_device,
|
||||
rect_resources,
|
||||
load_op,
|
||||
texture_size,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw(
|
||||
bounds: Bounds,
|
||||
content: DrawableContent,
|
||||
pos: Vector2<f32>,
|
||||
staging_belt: &mut wgpu::util::StagingBelt,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
cmd_encoder: &mut CommandEncoder,
|
||||
texture_view: &TextureView,
|
||||
gpu_device: &wgpu::Device,
|
||||
rect_resources: &RectResources,
|
||||
load_op: LoadOp<wgpu::Color>,
|
||||
texture_size: Bounds,
|
||||
) {
|
||||
use DrawableContent::*;
|
||||
|
||||
match content {
|
||||
Text(section, offset) => {
|
||||
glyph_brush.queue(section.with_screen_position(pos + offset).to_borrowed());
|
||||
|
||||
glyph_brush
|
||||
.draw_queued(
|
||||
gpu_device,
|
||||
staging_belt,
|
||||
cmd_encoder,
|
||||
texture_view,
|
||||
texture_size.width as u32, // TODO why do we make these be u32 and then cast to f32 in orthorgraphic_projection?
|
||||
texture_size.height as u32,
|
||||
)
|
||||
.expect("Failed to draw text element");
|
||||
}
|
||||
FillRect {
|
||||
color,
|
||||
border_width,
|
||||
border_color,
|
||||
} => {
|
||||
// TODO store all these colors and things in FillRect
|
||||
let rect_elt = RectElt {
|
||||
rect: Rect {
|
||||
pos,
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
color,
|
||||
border_width,
|
||||
border_color,
|
||||
};
|
||||
|
||||
// TODO inline draw_rects into here!
|
||||
draw_rects(
|
||||
&[rect_elt],
|
||||
cmd_encoder,
|
||||
texture_view,
|
||||
gpu_device,
|
||||
rect_resources,
|
||||
load_op,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// focused_elem is the currently-focused element (or NULL if nothing has the focus)
|
||||
fn to_drawable(
|
||||
elem: &RocElem,
|
||||
bounds: Bounds,
|
||||
glyph_brush: &mut GlyphBrush<()>,
|
||||
) -> (Bounds, Drawable) {
|
||||
use RocElemTag::*;
|
||||
|
||||
match elem.tag() {
|
||||
Rect => {
|
||||
let rect = unsafe { &elem.entry().rect };
|
||||
|
||||
let bounds = Bounds {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
|
||||
let drawable = Drawable {
|
||||
pos: (rect.left, rect.top).into(),
|
||||
bounds,
|
||||
content: DrawableContent::FillRect {
|
||||
color: rect.color,
|
||||
border_width: 1.0,
|
||||
border_color: rect.color,
|
||||
},
|
||||
};
|
||||
|
||||
(bounds, drawable)
|
||||
}
|
||||
Text => {
|
||||
let text = unsafe { &elem.entry().text };
|
||||
let is_centered = true; // TODO don't hardcode this
|
||||
let layout = wgpu_glyph::Layout::default().h_align(if is_centered {
|
||||
wgpu_glyph::HorizontalAlign::Center
|
||||
} else {
|
||||
wgpu_glyph::HorizontalAlign::Left
|
||||
});
|
||||
|
||||
let section = owned_section_from_str(text.text.as_str(),text.color, text.size, bounds, layout);
|
||||
|
||||
// Calculate the bounds and offset by measuring glyphs
|
||||
let text_bounds;
|
||||
let offset;
|
||||
|
||||
match glyph_brush.glyph_bounds(section.to_borrowed()) {
|
||||
Some(glyph_bounds) => {
|
||||
text_bounds = Bounds {
|
||||
width: glyph_bounds.max.x - glyph_bounds.min.x,
|
||||
height: glyph_bounds.max.y - glyph_bounds.min.y,
|
||||
};
|
||||
|
||||
offset = (-glyph_bounds.min.x, -glyph_bounds.min.y).into();
|
||||
}
|
||||
None => {
|
||||
text_bounds = Bounds {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
};
|
||||
|
||||
offset = (0.0, 0.0).into();
|
||||
}
|
||||
}
|
||||
|
||||
let drawable = Drawable {
|
||||
pos: (text.left, text.top).into(),
|
||||
bounds: text_bounds,
|
||||
content: DrawableContent::Text(section, offset),
|
||||
};
|
||||
|
||||
(text_bounds, drawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn owned_section_from_str(
|
||||
string: &str,
|
||||
color: Rgba,
|
||||
size: f32,
|
||||
bounds: Bounds,
|
||||
layout: wgpu_glyph::Layout<wgpu_glyph::BuiltInLineBreaker>,
|
||||
) -> OwnedSection {
|
||||
OwnedSection {
|
||||
bounds: (bounds.width, bounds.height),
|
||||
layout,
|
||||
..OwnedSection::default()
|
||||
}
|
||||
.add_text(
|
||||
glyph_brush::OwnedText::new(string)
|
||||
.with_color(Vector4::from(color))
|
||||
.with_scale(size),
|
||||
)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
#![allow(unused)]
|
||||
|
||||
mod graphics;
|
||||
mod gui;
|
||||
mod roc;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main() -> i32 {
|
||||
let bounds = roc::Bounds {
|
||||
width: 1900.0,
|
||||
height: 1000.0,
|
||||
};
|
||||
|
||||
gui::run_event_loop("RocOut!", bounds).expect("Error running event loop");
|
||||
|
||||
// Exit code
|
||||
0
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
std::process::exit(host::rust_main() as _);
|
||||
}
|
@ -1,453 +0,0 @@
|
||||
use crate::graphics::colors::Rgba;
|
||||
use core::alloc::Layout;
|
||||
use core::ffi::c_void;
|
||||
use core::mem::{self, ManuallyDrop};
|
||||
use roc_std::{RocList, RocStr};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::Debug;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::c_char;
|
||||
use std::time::Duration;
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
extern "C" {
|
||||
// program
|
||||
|
||||
// #[link_name = "roc__programForHost_1_exposed_generic"]
|
||||
// fn roc_program();
|
||||
|
||||
// #[link_name = "roc__programForHost_1_exposed_size"]
|
||||
// fn roc_program_size() -> i64;
|
||||
|
||||
// init
|
||||
|
||||
#[link_name = "roc__programForHost_0_caller"]
|
||||
fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model);
|
||||
|
||||
#[link_name = "roc__programForHost_0_size"]
|
||||
fn init_size() -> i64;
|
||||
|
||||
#[link_name = "roc__programForHost_0_result_size"]
|
||||
fn init_result_size() -> i64;
|
||||
|
||||
// update
|
||||
|
||||
#[link_name = "roc__programForHost_1_caller"]
|
||||
fn call_update(
|
||||
model: *const Model,
|
||||
event: *const RocEvent,
|
||||
closure_data: *const u8,
|
||||
output: *mut Model,
|
||||
);
|
||||
|
||||
#[link_name = "roc__programForHost_1_size"]
|
||||
fn update_size() -> i64;
|
||||
|
||||
#[link_name = "roc__programForHost_1_result_size"]
|
||||
fn update_result_size() -> i64;
|
||||
|
||||
// render
|
||||
|
||||
#[link_name = "roc__programForHost_2_caller"]
|
||||
fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList<RocElem>);
|
||||
|
||||
#[link_name = "roc__programForHost_2_size"]
|
||||
fn roc_render_size() -> i64;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union RocEventEntry {
|
||||
pub key_down: RocKeyCode,
|
||||
pub key_up: RocKeyCode,
|
||||
pub resize: Bounds,
|
||||
pub tick: [u8; 16], // u128 is unsupported in repr(C)
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocEventTag {
|
||||
KeyDown = 0,
|
||||
KeyUp,
|
||||
Resize,
|
||||
Tick,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||
pub struct RocEvent {
|
||||
entry: RocEventEntry,
|
||||
tag: RocEventTag,
|
||||
}
|
||||
|
||||
impl Debug for RocEvent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use RocEventTag::*;
|
||||
|
||||
match self.tag() {
|
||||
KeyDown => unsafe { self.entry().key_down }.fmt(f),
|
||||
KeyUp => unsafe { self.entry().key_up }.fmt(f),
|
||||
Resize => unsafe { self.entry().resize }.fmt(f),
|
||||
Tick => unsafe { self.entry().tick }.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RocEvent {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub fn tag(&self) -> RocEventTag {
|
||||
self.tag
|
||||
}
|
||||
|
||||
pub fn entry(&self) -> &RocEventEntry {
|
||||
&self.entry
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Resize(size: Bounds) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::Resize,
|
||||
entry: RocEventEntry { resize: size },
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn KeyDown(keycode: RocKeyCode) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::KeyDown,
|
||||
entry: RocEventEntry { key_down: keycode },
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn KeyUp(keycode: RocKeyCode) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::KeyUp,
|
||||
entry: RocEventEntry { key_up: keycode },
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Tick(duration: Duration) -> Self {
|
||||
Self {
|
||||
tag: RocEventTag::Tick,
|
||||
entry: RocEventEntry {
|
||||
tick: duration.as_nanos().to_ne_bytes(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocKeyCode {
|
||||
Down = 0,
|
||||
Left,
|
||||
Other,
|
||||
Right,
|
||||
Up,
|
||||
}
|
||||
|
||||
impl From<VirtualKeyCode> for RocKeyCode {
|
||||
fn from(keycode: VirtualKeyCode) -> Self {
|
||||
use VirtualKeyCode::*;
|
||||
|
||||
match keycode {
|
||||
Left => RocKeyCode::Left,
|
||||
Right => RocKeyCode::Right,
|
||||
Up => RocKeyCode::Up,
|
||||
Down => RocKeyCode::Down,
|
||||
_ => RocKeyCode::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||
return libc::malloc(size);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_realloc(
|
||||
c_ptr: *mut c_void,
|
||||
new_size: usize,
|
||||
_old_size: usize,
|
||||
_alignment: u32,
|
||||
) -> *mut c_void {
|
||||
return libc::realloc(c_ptr, new_size);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||
return libc::free(c_ptr);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_panic(msg: *mut RocStr, tag_id: u32) {
|
||||
match tag_id {
|
||||
0 => {
|
||||
eprintln!("Roc standard library hit a panic: {}", &*msg);
|
||||
}
|
||||
1 => {
|
||||
eprintln!("Application hit a panic: {}", &*msg);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_dbg(loc: *mut RocStr, msg: *mut RocStr, src: *mut RocStr) {
|
||||
eprintln!("[{}] {} = {}", &*loc, &*src, &*msg);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
|
||||
libc::memset(dst, c, n)
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ElemId(*const RocElemEntry);
|
||||
|
||||
#[repr(C)]
|
||||
pub union RocElemEntry {
|
||||
pub rect: ManuallyDrop<RocRect>,
|
||||
pub text: ManuallyDrop<RocText>,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RocElemTag {
|
||||
Rect = 0,
|
||||
Text = 1,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
|
||||
pub struct RocElem {
|
||||
entry: RocElemEntry,
|
||||
tag: RocElemTag,
|
||||
}
|
||||
|
||||
impl Debug for RocElem {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use RocElemTag::*;
|
||||
|
||||
match self.tag() {
|
||||
Rect => unsafe { &*self.entry().rect }.fmt(f),
|
||||
Text => unsafe { &*self.entry().text }.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RocElem {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub fn tag(&self) -> RocElemTag {
|
||||
self.tag
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn entry(&self) -> &RocElemEntry {
|
||||
&self.entry
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rect(styles: ButtonStyles) -> RocElem {
|
||||
todo!("restore rect() method")
|
||||
// let rect = RocRect { styles };
|
||||
// let entry = RocElemEntry {
|
||||
// rect: ManuallyDrop::new(rect),
|
||||
// };
|
||||
|
||||
// Self::elem_from_tag(entry, RocElemTag::Rect)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn text<T: Into<RocStr>>(into_roc_str: T) -> RocElem {
|
||||
todo!("TODO restore text method")
|
||||
// let entry = RocElemEntry {
|
||||
// text: ManuallyDrop::new(into_roc_str.into()),
|
||||
// };
|
||||
|
||||
// Self::elem_from_tag(entry, RocElemTag::Text)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RocRect {
|
||||
pub color: Rgba,
|
||||
|
||||
// These must be in this order for alphabetization!
|
||||
pub height: f32,
|
||||
pub left: f32,
|
||||
pub top: f32,
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RocText {
|
||||
pub text: RocStr,
|
||||
pub color: Rgba,
|
||||
pub left: f32,
|
||||
pub size: f32,
|
||||
pub top: f32,
|
||||
}
|
||||
|
||||
impl Clone for RocElem {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
match self.tag() {
|
||||
RocElemTag::Rect => Self {
|
||||
tag: RocElemTag::Rect,
|
||||
entry: RocElemEntry {
|
||||
rect: self.entry.rect.clone(),
|
||||
},
|
||||
},
|
||||
RocElemTag::Text => Self {
|
||||
tag: RocElemTag::Text,
|
||||
entry: RocElemEntry {
|
||||
text: self.entry.text.clone(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RocElem {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match self.tag() {
|
||||
RocElemTag::Rect => mem::drop(ManuallyDrop::take(&mut self.entry.rect)),
|
||||
RocElemTag::Text => mem::drop(ManuallyDrop::take(&mut self.entry.text)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ButtonStyles {
|
||||
pub bg_color: Rgba,
|
||||
pub border_color: Rgba,
|
||||
pub border_width: f32,
|
||||
pub text_color: Rgba,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Bounds {
|
||||
pub height: f32,
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
type Model = c_void;
|
||||
|
||||
/// Call the app's init function, then render and return that result
|
||||
pub fn init_and_render(bounds: Bounds) -> (*const Model, RocList<RocElem>) {
|
||||
let closure_data_buf;
|
||||
let closure_layout;
|
||||
|
||||
// Call init to get the initial model
|
||||
let model = unsafe {
|
||||
let ret_val_layout = Layout::array::<u8>(init_result_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
|
||||
|
||||
closure_layout = Layout::array::<u8>(init_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
closure_data_buf = std::alloc::alloc(closure_layout);
|
||||
|
||||
call_init(&bounds, closure_data_buf, ret_val_buf);
|
||||
|
||||
ret_val_buf
|
||||
};
|
||||
|
||||
// Call render passing the model to get the initial Elems
|
||||
let elems = unsafe {
|
||||
let mut ret_val: MaybeUninit<RocList<RocElem>> = MaybeUninit::uninit();
|
||||
|
||||
// Reuse the buffer from the previous closure if possible
|
||||
let closure_data_buf =
|
||||
std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize);
|
||||
|
||||
call_render(model, closure_data_buf, ret_val.as_mut_ptr());
|
||||
|
||||
std::alloc::dealloc(closure_data_buf, closure_layout);
|
||||
|
||||
ret_val.assume_init()
|
||||
};
|
||||
|
||||
(model, elems)
|
||||
}
|
||||
|
||||
/// Call the app's update function, then render and return that result
|
||||
pub fn update(model: *const Model, event: RocEvent) -> *const Model {
|
||||
let closure_data_buf;
|
||||
let closure_layout;
|
||||
|
||||
// Call update to get the new model
|
||||
unsafe {
|
||||
let ret_val_layout = Layout::array::<u8>(update_result_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
|
||||
|
||||
closure_layout = Layout::array::<u8>(update_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
closure_data_buf = std::alloc::alloc(closure_layout);
|
||||
|
||||
call_update(model, &event, closure_data_buf, ret_val_buf);
|
||||
|
||||
ret_val_buf
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the app's update function, then render and return that result
|
||||
pub fn update_and_render(model: *const Model, event: RocEvent) -> (*const Model, RocList<RocElem>) {
|
||||
let closure_data_buf;
|
||||
let closure_layout;
|
||||
|
||||
// Call update to get the new model
|
||||
let model = unsafe {
|
||||
let ret_val_layout = Layout::array::<u8>(update_result_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
let ret_val_buf = std::alloc::alloc(ret_val_layout) as *mut Model;
|
||||
|
||||
closure_layout = Layout::array::<u8>(update_size() as usize).unwrap();
|
||||
|
||||
// TODO allocate on the stack if it's under a certain size
|
||||
closure_data_buf = std::alloc::alloc(closure_layout);
|
||||
|
||||
call_update(model, &event, closure_data_buf, ret_val_buf);
|
||||
|
||||
ret_val_buf
|
||||
};
|
||||
|
||||
// Call render passing the model to get the initial Elems
|
||||
let elems = unsafe {
|
||||
let mut ret_val: MaybeUninit<RocList<RocElem>> = MaybeUninit::uninit();
|
||||
|
||||
// Reuse the buffer from the previous closure if possible
|
||||
let closure_data_buf =
|
||||
std::alloc::realloc(closure_data_buf, closure_layout, roc_render_size() as usize);
|
||||
|
||||
call_render(model, closure_data_buf, ret_val.as_mut_ptr());
|
||||
|
||||
std::alloc::dealloc(closure_data_buf, closure_layout);
|
||||
|
||||
ret_val.assume_init()
|
||||
};
|
||||
|
||||
(model, elems)
|
||||
}
|
Loading…
Reference in New Issue
Block a user