mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Merge pull request #6770 from roc-lang/move-basic-cli-tests
Move basic cli tests from examples
This commit is contained in:
commit
f10f18980d
43 changed files with 20 additions and 2254 deletions
|
@ -1,7 +1,7 @@
|
|||
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
|
||||
|
||||
import pf.Stdout
|
||||
import "../../LICENSE" as license
|
||||
import "ingested-file.roc" as license
|
||||
|
||||
main =
|
||||
license
|
|
@ -1,7 +1,7 @@
|
|||
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br" }
|
||||
|
||||
import pf.Stdout
|
||||
import "../../LICENSE" as license : _ # A type hole can also be used here.
|
||||
import "ingested-file.roc" as license : _ # A type hole can also be used here.
|
||||
|
||||
main =
|
||||
# Due to how license is used, it will be a List U8.
|
|
@ -478,7 +478,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn hello_world() {
|
||||
test_roc_app_slim(
|
||||
"examples",
|
||||
"crates/cli/tests/cli",
|
||||
"helloWorld.roc",
|
||||
"Hello, World!\n",
|
||||
UseValgrind::Yes,
|
||||
|
@ -716,29 +716,6 @@ mod cli_run {
|
|||
test_roc_app_slim("examples/gui", "hello-guiBROKEN.roc", "", UseValgrind::No)
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, ignore)] // flaky error; issue #5024
|
||||
#[serial(breakout)]
|
||||
#[test]
|
||||
fn breakout() {
|
||||
test_roc_app_slim(
|
||||
"examples/gui/breakout",
|
||||
"breakoutBROKEN.roc",
|
||||
"",
|
||||
UseValgrind::No,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(breakout)]
|
||||
fn breakout_hello_gui() {
|
||||
test_roc_app_slim(
|
||||
"examples/gui/breakout",
|
||||
"hello-guiBROKEN.roc",
|
||||
"",
|
||||
UseValgrind::No,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, ignore)]
|
||||
fn quicksort() {
|
||||
|
@ -778,7 +755,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")]
|
||||
#[serial(cli_platform)]
|
||||
fn cli_args_check() {
|
||||
let path = file_path_from_root("examples/cli", "argsBROKEN.roc");
|
||||
let path = file_path_from_root("crates/cli/tests/cli", "argsBROKEN.roc");
|
||||
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
@ -805,7 +782,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
#[serial(cli_platform)]
|
||||
fn cli_countdown_check() {
|
||||
let path = file_path_from_root("examples/cli", "countdown.roc");
|
||||
let path = file_path_from_root("crates/cli/tests/cli", "countdown.roc");
|
||||
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
@ -814,7 +791,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
#[serial(cli_platform)]
|
||||
fn cli_echo_check() {
|
||||
let path = file_path_from_root("examples/cli", "echo.roc");
|
||||
let path = file_path_from_root("crates/cli/tests/cli", "echo.roc");
|
||||
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
@ -823,7 +800,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
#[serial(cli_platform)]
|
||||
fn cli_file_check() {
|
||||
let path = file_path_from_root("examples/cli", "fileBROKEN.roc");
|
||||
let path = file_path_from_root("crates/cli/tests/cli", "fileBROKEN.roc");
|
||||
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
@ -832,7 +809,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
#[serial(cli_platform)]
|
||||
fn cli_form_check() {
|
||||
let path = file_path_from_root("examples/cli", "form.roc");
|
||||
let path = file_path_from_root("crates/cli/tests/cli", "form.roc");
|
||||
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
@ -841,7 +818,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
#[serial(cli_platform)]
|
||||
fn cli_http_get_check() {
|
||||
let path = file_path_from_root("examples/cli", "http-get.roc");
|
||||
let path = file_path_from_root("crates/cli/tests/cli", "http-get.roc");
|
||||
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
@ -917,7 +894,7 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn with_env_vars() {
|
||||
test_roc_app(
|
||||
"examples/cli",
|
||||
"crates/cli/tests/cli",
|
||||
"env.roc",
|
||||
&[],
|
||||
&[],
|
||||
|
@ -939,14 +916,14 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn ingested_file() {
|
||||
test_roc_app(
|
||||
"examples/cli",
|
||||
"crates/cli/tests/cli",
|
||||
"ingested-file.roc",
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
format!(
|
||||
"\nThis roc file can print its own source code. The source is:\n\n{}\n",
|
||||
include_str!("../../../examples/cli/ingested-file.roc")
|
||||
include_str!("cli/ingested-file.roc")
|
||||
)
|
||||
.as_str(),
|
||||
UseValgrind::No,
|
||||
|
@ -959,12 +936,12 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn ingested_file_bytes() {
|
||||
test_roc_app(
|
||||
"examples/cli",
|
||||
"crates/cli/tests/cli",
|
||||
"ingested-file-bytes.roc",
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
"162088\n",
|
||||
"27101\n",
|
||||
UseValgrind::No,
|
||||
TestCliCommands::Run,
|
||||
)
|
||||
|
@ -974,12 +951,12 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn ingested_file_bytes_no_ann() {
|
||||
test_roc_app(
|
||||
"examples/cli",
|
||||
"crates/cli/tests/cli",
|
||||
"ingested-file-bytes-no-ann.roc",
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
"162088\n",
|
||||
"27101\n",
|
||||
UseValgrind::No,
|
||||
TestCliCommands::Run,
|
||||
)
|
||||
|
@ -990,8 +967,8 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_movies_csv() {
|
||||
test_roc_app_slim(
|
||||
"examples/parser",
|
||||
"parse-movies-csv.roc",
|
||||
"crates/cli/tests/cli",
|
||||
"parser-movies-csv.roc",
|
||||
"2 movies were found:\n\nThe movie 'Airplane!' was released in 1980 and stars Robert Hays and Julie Hagerty\nThe movie 'Caddyshack' was released in 1980 and stars Chevy Chase, Rodney Dangerfield, Ted Knight, Michael O'Keefe and Bill Murray\n\nParse success!\n\n",
|
||||
UseValgrind::No,
|
||||
)
|
||||
|
@ -1002,8 +979,8 @@ mod cli_run {
|
|||
#[cfg_attr(windows, ignore)]
|
||||
fn parse_letter_counts() {
|
||||
test_roc_app_slim(
|
||||
"examples/parser",
|
||||
"letter-counts.roc",
|
||||
"crates/cli/tests/cli",
|
||||
"parser-letter-counts.roc",
|
||||
"I counted 7 letter A's!\n",
|
||||
UseValgrind::No,
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
The parser package example has been moved to [https://github.com/lukewilliamboswell/roc-parser](https://github.com/lukewilliamboswell/roc-parser).
|
||||
|
||||
The examples here use a release of this package.
|
|
@ -1,81 +0,0 @@
|
|||
app [main] { pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.1/dCL3KsovvV-8A5D_W_0X_abynkcRcoAngsgF0xtvQsk.tar.br" }
|
||||
|
||||
import pf.Stdout
|
||||
import pf.Stderr
|
||||
import pf.Task exposing [Task]
|
||||
import pf.Http exposing [Request, Response]
|
||||
import pf.Utc
|
||||
import pf.Env
|
||||
|
||||
main : Request -> Task Response []
|
||||
main = \req ->
|
||||
|
||||
handleReq =
|
||||
# Log the date, time, method, and url to stdout
|
||||
{} <- logRequest req |> Task.await
|
||||
|
||||
# Read environment variable
|
||||
url <- readUrlEnv "TARGET_URL" |> Task.await
|
||||
|
||||
# Fetch the Roc website
|
||||
content <- fetchContent url |> Task.await
|
||||
|
||||
# Respond with the website content
|
||||
respond 200 content
|
||||
|
||||
# Handle any application errors
|
||||
handleReq |> Task.onErr handleErr
|
||||
|
||||
AppError : [
|
||||
EnvURLNotFound,
|
||||
HttpError Http.Error,
|
||||
]
|
||||
|
||||
logRequest : Request -> Task {} AppError
|
||||
logRequest = \req ->
|
||||
dateTime <- Utc.now |> Task.map Utc.toIso8601Str |> Task.await
|
||||
|
||||
Stdout.line "$(dateTime) $(Http.methodToStr req.method) $(req.url)"
|
||||
|
||||
readUrlEnv : Str -> Task Str AppError
|
||||
readUrlEnv = \target ->
|
||||
Env.var target
|
||||
|> Task.mapErr \_ -> EnvURLNotFound
|
||||
|
||||
fetchContent : Str -> Task Str AppError
|
||||
fetchContent = \url ->
|
||||
Http.getUtf8 url
|
||||
|> Task.mapErr \err -> HttpError err
|
||||
|
||||
handleErr : AppError -> Task Response []
|
||||
handleErr = \err ->
|
||||
|
||||
# Build error message
|
||||
message =
|
||||
when err is
|
||||
EnvURLNotFound -> "TARGET_URL environment variable not set"
|
||||
HttpError _ -> "Http error fetching content"
|
||||
|
||||
# Log error to stderr
|
||||
{} <- Stderr.line "Internal Server Error: $(message)" |> Task.await
|
||||
_ <- Stderr.flush |> Task.attempt
|
||||
|
||||
# Respond with Http 500 Error
|
||||
Task.ok {
|
||||
status: 500,
|
||||
headers: [
|
||||
{ name: "Content-Type", value: Str.toUtf8 "text/html; charset=utf-8" },
|
||||
],
|
||||
body: Str.toUtf8 "Error 500 Internal Server Error\n",
|
||||
}
|
||||
|
||||
# Respond with the given status code and body
|
||||
respond : U16, Str -> Task Response AppError
|
||||
respond = \code, body ->
|
||||
Task.ok {
|
||||
status: code,
|
||||
headers: [
|
||||
{ name: "Content-Type", value: Str.toUtf8 "text/html; charset=utf-8" },
|
||||
],
|
||||
body: Str.toUtf8 body,
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
app [main] { pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.1/dCL3KsovvV-8A5D_W_0X_abynkcRcoAngsgF0xtvQsk.tar.br" }
|
||||
|
||||
import pf.Stdout
|
||||
import pf.Task exposing [Task]
|
||||
import pf.Http exposing [Request, Response]
|
||||
import pf.Utc
|
||||
|
||||
main : Request -> Task Response []
|
||||
main = \req ->
|
||||
|
||||
# Log request date, method and url
|
||||
date <- Utc.now |> Task.map Utc.toIso8601Str |> Task.await
|
||||
{} <- Stdout.line "$(date) $(Http.methodToStr req.method) $(req.url)" |> Task.await
|
||||
|
||||
# Respond with request body
|
||||
when req.body is
|
||||
EmptyBody -> Task.ok { status: 200, headers: [], body: [] }
|
||||
Body internal -> Task.ok { status: 200, headers: [], body: internal.body }
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue