initial commit
This commit is contained in:
commit
4def485a6d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
build
|
||||||
|
resources/shaders/compiled/**
|
||||||
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[submodule "lib/ugui.c3l"]
|
||||||
|
path = lib/ugui.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/ugui.c3l.git
|
||||||
|
[submodule "lib/schrift.c3l"]
|
||||||
|
path = lib/schrift.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/schrift.c3l.git
|
||||||
|
[submodule "lib/ugui_sdl.c3l"]
|
||||||
|
path = lib/ugui_sdl.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/ugui_sdl.c3l.git
|
||||||
|
[submodule "lib/sdl3.c3l"]
|
||||||
|
path = lib/sdl3.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/sdl3.c3l.git
|
||||||
16
example-vm.json
Normal file
16
example-vm.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name" : "Alpine Linux",
|
||||||
|
"disk" : "$HOME/Documents/alpine.qcow2",
|
||||||
|
"memory" : "1G",
|
||||||
|
"processors" : "2",
|
||||||
|
"qemu" : "qemu-system-x86_64",
|
||||||
|
"parameters" : [
|
||||||
|
"enable-kvm",
|
||||||
|
[ "net", "nic" ],
|
||||||
|
[ "net", "user" ],
|
||||||
|
[ "display", "sdl" ],
|
||||||
|
[ "vga", "qxl" ],
|
||||||
|
[ "monitor", "stdio" ],
|
||||||
|
[ "virtfs", "local,path=/home/ale/Documents/Projects/toughpad-alpine,mount_tag=shared,security_model=mapped-xattr,id=shared" ],
|
||||||
|
]
|
||||||
|
}
|
||||||
1
lib/schrift.c3l
Submodule
1
lib/schrift.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 64d168f53b0fe70bda77e2ed7bec35f3f0f1af78
|
||||||
1
lib/sdl3.c3l
Submodule
1
lib/sdl3.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e7356df5d1d0c22a6bba822bda6994062b0b75d7
|
||||||
1
lib/ugui.c3l
Submodule
1
lib/ugui.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 4f7fa7d50c16db3acac020cdd7204e28d42efdf2
|
||||||
1
lib/ugui_sdl.c3l
Submodule
1
lib/ugui_sdl.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 050624fd67c2d80190114c7774ccfa64b0f449b9
|
||||||
20
project.json
Normal file
20
project.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"langrev": "1",
|
||||||
|
"warnings": ["no-unused"],
|
||||||
|
"dependency-search-paths": ["lib"],
|
||||||
|
"dependencies": ["ugui", "ugui_sdl3"],
|
||||||
|
"features": ["DEBUG_POINTER"],
|
||||||
|
"authors": ["Alessandro Mauri <ale@shitposting.expert>"],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"sources": ["src/**"],
|
||||||
|
"output": "build",
|
||||||
|
"target": "linux-x64",
|
||||||
|
"targets": {
|
||||||
|
"ugui": {
|
||||||
|
"type": "executable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe": true,
|
||||||
|
"opt": "O1",
|
||||||
|
"debug-info": "full"
|
||||||
|
}
|
||||||
BIN
resources/hack-nerd.ttf
Normal file
BIN
resources/hack-nerd.ttf
Normal file
Binary file not shown.
33
resources/shaders/Makefile
Normal file
33
resources/shaders/Makefile
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
SOURCE_DIR := ./source
|
||||||
|
COMPILED_DIR := ./compiled
|
||||||
|
|
||||||
|
SOURCE_FILES := $(wildcard $(SOURCE_DIR)/*.glsl)
|
||||||
|
|
||||||
|
COMPILED_FILES := $(patsubst $(SOURCE_DIR)/%.glsl,$(COMPILED_DIR)/%.spv,$(SOURCE_FILES))
|
||||||
|
|
||||||
|
all: $(COMPILED_FILES)
|
||||||
|
@echo "Compiling shaders from $(SOURCE_DIR) -> $(COMPILED_DIR)"
|
||||||
|
|
||||||
|
$(COMPILED_DIR)/%.spv: $(SOURCE_DIR)/%.glsl
|
||||||
|
@mkdir -p $(COMPILED_DIR)
|
||||||
|
@stage=$$(basename $< .glsl | cut -d. -f2); \
|
||||||
|
if [ "$$stage" = "frag" ] || [ "$$stage" = "vert" ]; then \
|
||||||
|
echo "$$stage $(notdir $<) > $(notdir $@)"; \
|
||||||
|
glslc -O0 -g -fshader-stage=$$stage $< -o $@; \
|
||||||
|
else \
|
||||||
|
echo "Skipping $<: unsupported stage $$stage"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
$(COMPILED_DIR):
|
||||||
|
mkdir -p $(COMPILED_DIR)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf $(COMPILED_DIR)
|
||||||
|
|
||||||
|
.PHONY: tree
|
||||||
|
tree:
|
||||||
|
tree $(COMPILED_DIR)
|
||||||
|
|
||||||
|
.PHONY: compile_all
|
||||||
|
compile_all: clean all tree
|
||||||
118
resources/shaders/source/ugui.frag.glsl
Normal file
118
resources/shaders/source/ugui.frag.glsl
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
/* Combined fragment shader to render UGUI commands */
|
||||||
|
|
||||||
|
// type values, these are the same as in renderer.c3
|
||||||
|
const uint TYPE_RECT = 0;
|
||||||
|
const uint TYPE_FONT = 1;
|
||||||
|
const uint TYPE_SPRITE = 2;
|
||||||
|
const uint TYPE_MSDF = 3;
|
||||||
|
|
||||||
|
// viewport size
|
||||||
|
layout(set = 3, binding = 0) uniform Viewport {
|
||||||
|
ivec2 view;
|
||||||
|
};
|
||||||
|
|
||||||
|
// textures
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D font_atlas;
|
||||||
|
layout(set = 2, binding = 1) uniform sampler2D sprite_atlas;
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
layout(location = 0) in vec4 in_color;
|
||||||
|
layout(location = 1) in vec2 in_uv;
|
||||||
|
layout(location = 2) in vec4 in_quad_size;
|
||||||
|
layout(location = 3) in float in_radius;
|
||||||
|
layout(location = 4) flat in uint in_type;
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
|
||||||
|
// SDF for a rounded rectangle given the centerpoint, half size and radius, all in pixels
|
||||||
|
float sdf_rr(vec2 p, vec2 half_size, float radius) {
|
||||||
|
vec2 q = abs(p) - half_size + radius;
|
||||||
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const float PX_RANGE = 4.0f;
|
||||||
|
float screen_px_range(vec2 uv, sampler2D tx) {
|
||||||
|
vec2 unit_range = vec2(PX_RANGE)/vec2(textureSize(tx, 0));
|
||||||
|
vec2 texel_size = vec2(1.0)/fwidth(uv);
|
||||||
|
return max(0.5*dot(unit_range, texel_size), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float median(float r, float g, float b) {
|
||||||
|
return max(min(r, g), min(max(r, g), b));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_RECT, draw a rouded rectangle with a SDF
|
||||||
|
void rect_main()
|
||||||
|
{
|
||||||
|
vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5;
|
||||||
|
vec2 half_size = in_quad_size.zw * 0.5;
|
||||||
|
float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius);
|
||||||
|
float alpha = 1.0 - smoothstep(0.0, 1.5, distance);
|
||||||
|
fragColor = vec4(in_color.rgb, in_color.a * alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_SPRITE, draws a sprite sampled from an atlas
|
||||||
|
void sprite_main()
|
||||||
|
{
|
||||||
|
ivec2 ts = textureSize(sprite_atlas, 0);
|
||||||
|
vec2 fts = vec2(ts);
|
||||||
|
vec2 real_uv = in_uv.xy / fts;
|
||||||
|
fragColor = texture(sprite_atlas, real_uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_FONT, draws a character sampled from an atlas that contains only the alpha channel
|
||||||
|
void font_main()
|
||||||
|
{
|
||||||
|
ivec2 ts = textureSize(font_atlas, 0);
|
||||||
|
vec2 fts = vec2(ts);
|
||||||
|
vec2 real_uv = in_uv.xy / fts;
|
||||||
|
|
||||||
|
vec4 opacity = texture(font_atlas, real_uv);
|
||||||
|
fragColor = vec4(in_color.rgb, in_color.a*opacity.r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_MSDF, draws a sprite that is stored as a multi-channel SDF
|
||||||
|
void msdf_main() {
|
||||||
|
ivec2 ts = textureSize(sprite_atlas, 0);
|
||||||
|
vec2 fts = vec2(ts);
|
||||||
|
vec2 real_uv = in_uv.xy / fts;
|
||||||
|
|
||||||
|
vec3 msd = texture(sprite_atlas, real_uv).rgb;
|
||||||
|
float sd = median(msd.r, msd.g, msd.b);
|
||||||
|
float distance = screen_px_range(real_uv, sprite_atlas)*(sd - 0.5);
|
||||||
|
float opacity = clamp(distance + 0.5, 0.0, 1.0);
|
||||||
|
fragColor = in_color * opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// shader main
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
switch (in_type) {
|
||||||
|
case TYPE_RECT:
|
||||||
|
rect_main();
|
||||||
|
break;
|
||||||
|
case TYPE_FONT:
|
||||||
|
font_main();
|
||||||
|
break;
|
||||||
|
case TYPE_SPRITE:
|
||||||
|
sprite_main();
|
||||||
|
break;
|
||||||
|
case TYPE_MSDF:
|
||||||
|
msdf_main();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ERROR, invalid type, return magenta
|
||||||
|
fragColor = vec4(1.0, 0.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
resources/shaders/source/ugui.vert.glsl
Normal file
47
resources/shaders/source/ugui.vert.glsl
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
/* Combined vertex shader to render UGUI commands */
|
||||||
|
|
||||||
|
// Viewport size in pixels
|
||||||
|
layout(set = 1, binding = 0) uniform Viewport {
|
||||||
|
ivec2 view;
|
||||||
|
};
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
layout(location = 0) in ivec2 in_position;
|
||||||
|
layout(location = 1) in ivec4 in_attr; // quad x,y,w,h
|
||||||
|
layout(location = 2) in ivec4 in_uv;
|
||||||
|
layout(location = 3) in uvec4 in_color;
|
||||||
|
layout(location = 4) in uint in_type;
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
layout(location = 1) out vec2 out_uv;
|
||||||
|
layout(location = 2) out vec4 out_quad_size;
|
||||||
|
layout(location = 3) out float out_radius;
|
||||||
|
layout(location = 4) out uint out_type;
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// vertex position
|
||||||
|
ivec2 px_pos = in_attr.xy + in_position.xy * in_attr.zw;
|
||||||
|
vec2 clip_pos;
|
||||||
|
clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0;
|
||||||
|
clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0);
|
||||||
|
gl_Position = vec4(clip_pos, 0.0, 1.0);
|
||||||
|
|
||||||
|
// color output
|
||||||
|
out_color = vec4(in_color) / 255.0;
|
||||||
|
|
||||||
|
// uv output. only useful if the type is SPRITE
|
||||||
|
vec2 px_uv = in_uv.xy + in_position.xy * in_uv.zw;
|
||||||
|
out_uv = vec2(px_uv);
|
||||||
|
|
||||||
|
// quad size and radius output, only useful if type is RECT
|
||||||
|
out_quad_size = vec4(in_attr);
|
||||||
|
out_radius = float(abs(in_uv.x));
|
||||||
|
|
||||||
|
// type output
|
||||||
|
out_type = in_type;
|
||||||
|
}
|
||||||
88
resources/style.css
Normal file
88
resources/style.css
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
div {
|
||||||
|
bg: #282828;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #6c19ca8f;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
separator {
|
||||||
|
bg: #fbf1c7ff;
|
||||||
|
size: 1;
|
||||||
|
padding: 0 5 0 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 2;
|
||||||
|
border: 2;
|
||||||
|
padding: 2;
|
||||||
|
radius: 10;
|
||||||
|
size: 32;
|
||||||
|
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #504945ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbox {
|
||||||
|
margin: 2;
|
||||||
|
border: 2;
|
||||||
|
padding: 1;
|
||||||
|
radius: 10;
|
||||||
|
size: 20;
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle {
|
||||||
|
margin: 2;
|
||||||
|
border: 2;
|
||||||
|
padding: 1;
|
||||||
|
radius: 10;
|
||||||
|
size: 20;
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider {
|
||||||
|
margin: 2;
|
||||||
|
padding: 2;
|
||||||
|
border: 1;
|
||||||
|
radius: 4;
|
||||||
|
size: 8;
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar {
|
||||||
|
padding: 2;
|
||||||
|
size: 8;
|
||||||
|
bg: #45858842;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
text-box {
|
||||||
|
bg: #4a4543ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #8f3f61a8;
|
||||||
|
border: 1;
|
||||||
|
padding: 4;
|
||||||
|
margin: 2;
|
||||||
|
}
|
||||||
BIN
resources/tick_sdf.qoi
Normal file
BIN
resources/tick_sdf.qoi
Normal file
Binary file not shown.
85
src/conf.c3
Normal file
85
src/conf.c3
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import std::io;
|
||||||
|
import std::io::file;
|
||||||
|
import std::io::path;
|
||||||
|
import std::os::env;
|
||||||
|
import std::core::mem::allocator;
|
||||||
|
import std::collections::object;
|
||||||
|
import std::collections::list;
|
||||||
|
|
||||||
|
|
||||||
|
alias StrList = list::List{String};
|
||||||
|
|
||||||
|
fn Path? String.to_expanded_path(&str, Allocator allocator)
|
||||||
|
{
|
||||||
|
@pool() {
|
||||||
|
StrList l;
|
||||||
|
l.tinit();
|
||||||
|
Path str_path = str.to_tpath()!;
|
||||||
|
|
||||||
|
l.push(str_path.basename());
|
||||||
|
Path? p = str_path.parent();
|
||||||
|
while (true) {
|
||||||
|
Path? x;
|
||||||
|
if (try p) {
|
||||||
|
String n = p.basename();
|
||||||
|
|
||||||
|
// env variable
|
||||||
|
if (n.starts_with("$")) {
|
||||||
|
l.push(env::tget_var(n[1..]))!;
|
||||||
|
} else {
|
||||||
|
l.push(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
x = p.parent();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path path = path::temp("")!;
|
||||||
|
foreach_r (idx, s: l) {
|
||||||
|
path = path.tappend(s)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path::new(allocator, path.str_view(), path.env);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn StrList get_qemu_cmdline(Allocator allocator, Object* conf)
|
||||||
|
{
|
||||||
|
// parse the disk path
|
||||||
|
String disk = conf.get_string("disk")!!;
|
||||||
|
Path disk_path = disk.to_expanded_path(mem)!!;
|
||||||
|
defer disk_path.free();
|
||||||
|
|
||||||
|
// compose the command line for the vm
|
||||||
|
StrList cmd;
|
||||||
|
cmd.init(allocator);
|
||||||
|
|
||||||
|
// first the executable
|
||||||
|
cmd.push(conf.get_string("qemu"))!!;
|
||||||
|
// then the drive
|
||||||
|
cmd.push("-drive");
|
||||||
|
cmd.push(string::format(allocator, "file=%s", disk_path.str_view()));
|
||||||
|
//cmd.push(string::format(allocator, "file=%s,format=%s", disk_path.str_view(), disk_path.extension()))!!;
|
||||||
|
// memory
|
||||||
|
cmd.push("-m");
|
||||||
|
cmd.push(conf.get_string("memory"))!!;
|
||||||
|
// processors
|
||||||
|
cmd.push("-smp");
|
||||||
|
cmd.push(conf.get_string("processors") ?? "1");
|
||||||
|
// then all the parameters
|
||||||
|
Object* parameters = conf.get("parameters")!!;
|
||||||
|
for (usz i = 0; i < parameters.get_len(); i++) {
|
||||||
|
Object* p = parameters.get_at(i);
|
||||||
|
if (p.is_string()) {
|
||||||
|
cmd.push(string::format(allocator, "-%s", p.s));
|
||||||
|
} else if (p.is_indexable()) {
|
||||||
|
cmd.push(string::format(allocator, "-%s", p.get_string_at(0)))!!;
|
||||||
|
cmd.push(p.get_string_at(1))!!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
146
src/main.c3
Normal file
146
src/main.c3
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import std::io;
|
||||||
|
import std::io::file;
|
||||||
|
import std::collections::object;
|
||||||
|
import std::os::process;
|
||||||
|
import std::encoding::json;
|
||||||
|
import std::time;
|
||||||
|
|
||||||
|
import conf;
|
||||||
|
import ugui;
|
||||||
|
import ugui::sdl::ren;
|
||||||
|
|
||||||
|
|
||||||
|
const char[*] VS_PATH = "resources/shaders/compiled/ugui.vert.spv";
|
||||||
|
const char[*] FS_PATH = "resources/shaders/compiled/ugui.frag.spv";
|
||||||
|
|
||||||
|
const char[*] STYLESHEET_PATH = "resources/style.css";
|
||||||
|
|
||||||
|
const bool LIMIT_FPS = true;
|
||||||
|
const bool VSYNC = true;
|
||||||
|
|
||||||
|
|
||||||
|
fn int main(String[] args)
|
||||||
|
{
|
||||||
|
String path = args[1];
|
||||||
|
File file = file::open(path, "r")!!;
|
||||||
|
|
||||||
|
// load configuration in memory as an object
|
||||||
|
Object* conf = json::parse(mem, &file)!!;
|
||||||
|
defer conf.free();
|
||||||
|
|
||||||
|
// UI initialization
|
||||||
|
ArenaAllocator arena;
|
||||||
|
char[] arena_mem = mem::new_array(char, 1024*1024);
|
||||||
|
defer (void)mem::free(arena_mem);
|
||||||
|
arena.init(arena_mem);
|
||||||
|
|
||||||
|
ugui::Ctx ui;
|
||||||
|
ui.init(&arena)!!;
|
||||||
|
defer ui.free();
|
||||||
|
|
||||||
|
ren::Renderer ren;
|
||||||
|
ren.init("Qemu Manager", 800, 600, VSYNC);
|
||||||
|
defer ren.free();
|
||||||
|
ui.input_window_size(800, 600)!!;
|
||||||
|
|
||||||
|
ui.load_font(&arena, "font1", "resources/hack-nerd.ttf", 16)!!;
|
||||||
|
ren.font_atlas_id = ui.get_font_id("font1");
|
||||||
|
Atlas* font_atlas = ui.get_font_atlas("font1")!!;
|
||||||
|
ren.new_texture("font1", JUST_ALPHA, font_atlas.buffer, font_atlas.width, font_atlas.height);
|
||||||
|
|
||||||
|
ui.sprite_atlas_create("icons", AtlasType.ATLAS_R8G8B8A8, 512, 512)!!;
|
||||||
|
ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!;
|
||||||
|
ren.sprite_atlas_id = ui.get_sprite_atlas_id("icons");
|
||||||
|
Atlas* sprite_atlas = &(ui.sprite_atlas.atlas);
|
||||||
|
ren.new_texture("icons", FULL_COLOR, sprite_atlas.buffer, sprite_atlas.width, sprite_atlas.height);
|
||||||
|
|
||||||
|
ren.load_spirv_shader_from_file("UGUI_PIPELINE", VS_PATH, FS_PATH, 2, 0);
|
||||||
|
ren.create_pipeline("UGUI_PIPELINE", RECT);
|
||||||
|
|
||||||
|
ui.import_style_from_file(STYLESHEET_PATH);
|
||||||
|
ren::pre(ren.win);
|
||||||
|
// End UI initialization
|
||||||
|
|
||||||
|
StrList cmd = conf::get_qemu_cmdline(mem, conf);
|
||||||
|
defer cmd.free();
|
||||||
|
String vm_name = conf.get_string("name")!!;
|
||||||
|
String vm_disk = conf.get_string("disk").to_expanded_path(tmem).str_view()!!;
|
||||||
|
String vm_disk_size = bytes_to_human_readable(tmem, file::get_size(vm_disk))!!;
|
||||||
|
|
||||||
|
bool vm_on = false;
|
||||||
|
SubProcess vm_proc;
|
||||||
|
defer (void)vm_proc.join();
|
||||||
|
|
||||||
|
bool quit;
|
||||||
|
Clock sleep_clock;
|
||||||
|
while (!quit) {
|
||||||
|
sleep_clock.mark();
|
||||||
|
quit = ui.handle_events()!!;
|
||||||
|
vm_on = vm_proc.is_running()!!;
|
||||||
|
|
||||||
|
/* Start UI Handling */
|
||||||
|
ui.frame_begin()!!;
|
||||||
|
|
||||||
|
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;
|
||||||
|
|
||||||
|
ui.@div(ugui::@grow(), ugui::@grow(), COLUMN) {
|
||||||
|
ui.@div(ugui::@grow(), ugui::@fit(20)) {
|
||||||
|
ui.text("Machine:")!!;
|
||||||
|
ui.separator(ugui::@grow(), ugui::@grow())!!;
|
||||||
|
ui.text(vm_name)!!;
|
||||||
|
}!!;
|
||||||
|
ui.hor_line()!!;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@grow(), COLUMN, scroll_y: true) {
|
||||||
|
ui.text(string::tformat("disk: %s (%s)", vm_disk, vm_disk_size))!!;
|
||||||
|
ui.text(string::tformat("runner: %s", conf.get_string("qemu")))!!;
|
||||||
|
ui.text(string::tformat("memory: %s", conf.get_string("memory")))!!;
|
||||||
|
ui.text(string::tformat("processors: %s", conf.get_string("processors")))!!;
|
||||||
|
}!!;
|
||||||
|
ui.hor_line()!!;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@fit(), ROW) {
|
||||||
|
if (!vm_on) {
|
||||||
|
if (ui.button("START")!!.mouse_release) {
|
||||||
|
vm_proc = process::create(cmd.array_view(), {.inherit_stdio=true, .inherit_environment=true})!!;
|
||||||
|
vm_on = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ui.button("STOP")!!.mouse_release) {
|
||||||
|
vm_proc.terminate()!!;
|
||||||
|
vm_on = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}!!;
|
||||||
|
}!!;
|
||||||
|
|
||||||
|
ui.frame_end()!!;
|
||||||
|
/* End UI Handling */
|
||||||
|
|
||||||
|
/* Start UI Drawing */
|
||||||
|
ren.begin_render(true);
|
||||||
|
ren.render_ugui(&ui.cmd_queue);
|
||||||
|
ren.end_render();
|
||||||
|
/* End Drawing */
|
||||||
|
|
||||||
|
// wait for the next event, timeout after 100ms
|
||||||
|
int timeout = LIMIT_FPS ? (int)(100.0-sleep_clock.mark().to_ms()-0.5) : 0;
|
||||||
|
if (ui.skip_frame) timeout = 0;
|
||||||
|
ren::wait_events(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn String bytes_to_human_readable(Allocator allocator, usz bytes, int dp = 1) {
|
||||||
|
static usz thresh = 1024;
|
||||||
|
static String[] units = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"};
|
||||||
|
|
||||||
|
double b = bytes;
|
||||||
|
int u = 0;
|
||||||
|
for (; b > thresh && u < units.len; u++) {
|
||||||
|
b /= thresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string::format(allocator, "%.*f %s", dp, b, units[u]);
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user