remove breakout example

This commit is contained in:
Luke Boswell 2024-05-22 13:45:34 +10:00
parent b775c0e509
commit 3a38809360
No known key found for this signature in database
GPG Key ID: F6DB3C9DB47377B0
26 changed files with 0 additions and 2108 deletions

View File

@ -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 }

View File

@ -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 }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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=.");
}

View File

@ -1,3 +0,0 @@
extern int rust_main();
int main() { return rust_main(); }

View File

@ -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

View File

@ -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)
}
}

View File

@ -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,
}
}

View File

@ -1,5 +0,0 @@
pub mod buffer;
pub mod ortho;
pub mod pipelines;
pub mod vertex;
pub mod quad;

View File

@ -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,
}
}

View File

@ -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,
})
}

View File

@ -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,
),
};
}

View File

@ -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,
},
],
};
}

View File

@ -1,4 +0,0 @@
pub mod colors;
pub mod lowlevel;
pub mod primitives;
pub mod style;

View File

@ -1,2 +0,0 @@
pub mod rect;
pub mod text;

View File

@ -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,
}

View File

@ -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))
}

View File

@ -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;
}

View File

@ -1 +0,0 @@
pub const DEFAULT_FONT_SIZE: f32 = 30.0;

View File

@ -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),
)
}

View File

@ -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
}

View File

@ -1,3 +0,0 @@
fn main() {
std::process::exit(host::rust_main() as _);
}

View File

@ -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)
}