This commit is contained in:
Snorre 2025-02-13 16:50:36 +01:00
parent bc9ade91fe
commit 09eb8f134c
7 changed files with 758 additions and 768 deletions

897
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,11 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clay-layout = "0.2.0"
# clay-layout = "0.2.0"
clay-layout = { path = "../clay-ui-rust" }
enum_dispatch = "0.3.13"
font-kit = "0.14.2"
minifb = "0.28.0"
raqote = "0.8.5"
raylib = { version = "5.0.2", features = ["wayland"] }
test-case = "3.3.1"
[lib]

Binary file not shown.

View file

@ -9,48 +9,58 @@
};
};
outputs = {
self,
nixpkgs,
rust-overlay,
}: let
# rust-overlay = import rust-overlay;
pkgs =
import nixpkgs
{
system = "x86_64-linux";
overlays = [rust-overlay.overlays.default];
outputs =
{ self
, nixpkgs
, rust-overlay
,
}:
let
# rust-overlay = import rust-overlay;
pkgs =
import nixpkgs
{
system = "x86_64-linux";
overlays = [ rust-overlay.overlays.default ];
};
in
{
devShells.x86_64-linux.default = pkgs.mkShell rec {
buildInputs = with pkgs; [
rust-bin.nightly.latest.default
rust-analyzer
expat
fontconfig
freetype
freetype.dev
libGL
pkg-config
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
wayland
libxkbcommon
rust-cbindgen
cmake
glfw
clang
libclang
libllvm
nerd-fonts.mononoki
];
LD_LIBRARY_PATH =
builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
NIX_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
shellHook = ''
export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
export LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
# export MONOSPACE_FONT="${pkgs.nerd-fonts.mononoki}/share/fonts/truetype/NerdFonts/Mononoki/MononokiNerdFont-Regular.ttf "
'';
};
in {
devShells.x86_64-linux.default = pkgs.mkShell rec {
buildInputs = with pkgs; [
rust-bin.nightly.latest.default
rust-analyzer
expat
fontconfig
freetype
freetype.dev
libGL
pkg-config
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
wayland
libxkbcommon
rust-cbindgen
];
LD_LIBRARY_PATH =
builtins.foldl' (a: b: "${a}:${b}/lib") "${pkgs.vulkan-loader}/lib" buildInputs;
# NIX_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
shellHook = ''
# export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
# export LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
'';
};
};
}

View file

@ -1,140 +1,411 @@
use std::{any::Any, cell::RefCell, rc::Rc};
use clay_layout::{
Clay,
bindings::Clay_TextElementConfig,
elements::{
CornerRadius,
rectangle::Rectangle,
text::{Text, TextElementConfig},
},
fixed,
id::Id,
layout::Layout,
math::Dimensions,
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
rc::Rc,
};
use font_kit::{family_name::FamilyName, properties::Properties, source::SystemSource};
use gui::GUI;
use minifb::{Window, WindowOptions};
use raqote::{DrawOptions, DrawTarget, SolidSource};
use clay_layout::{
Declaration,
bindings::Clay_ResetMeasureTextCache,
fit, grow,
id::Id,
layout::{LayoutDirection, Padding},
math::Dimensions,
render_commands::RenderCommandConfig,
text::{TextConfig, TextElementConfig},
};
use raylib::{
RaylibHandle, RaylibThread,
color::Color,
ffi::Vector2,
prelude::{Font, RaylibDraw, RaylibDrawHandle, RaylibScissorModeExt},
text::RaylibFont,
};
mod gui;
struct FontStore {
font: String,
fonts: HashMap<i32, Rc<Font>>,
fonts_to_be_loaded: HashSet<i32>,
}
fn main() {
let clay = Clay::new((800., 600.).into());
impl FontStore {
const SIZE_PARAGRAPH: f32 = 20.;
const SIZE_HEADER: f32 = 30.;
const MOD_SUBTEXT: f32 = 0.5;
let mut window = Window::new("Raqote", 800, 40, WindowOptions {
borderless: true,
scale_mode: minifb::ScaleMode::Center,
resize: true,
..Default::default()
})
.unwrap();
let font = SystemSource::new()
.select_best_match(&[FamilyName::Monospace], &Properties::new())
.unwrap()
.load()
.unwrap();
window.set_target_fps(60);
let mut size = window.get_size();
let mut dt = DrawTarget::new(size.0 as i32, size.1 as i32);
clay.measure_text_function(|text, config| {
let config: Clay_TextElementConfig = unsafe {
*std::mem::transmute::<TextElementConfig, *mut Clay_TextElementConfig>(config)
};
Dimensions {
width: config.fontSize as f32,
height: config.fontSize as f32 * text.len() as f32,
pub fn new(name: impl Into<String>) -> Self {
Self {
fonts: HashMap::new(),
font: name.into(),
fonts_to_be_loaded: HashSet::new(),
}
});
}
loop {
dt.clear(SolidSource::from_unpremultiplied_argb(
0xff, 0xff, 0xff, 0xff,
));
clay.begin();
// Adds a red rectangle with a corner radius of 5.
// The Layout makes the rectangle have a width and height of 50.
clay.with(
[
Id::new("red_rectangle"),
Layout::new().width(fixed!(50.)).height(fixed!(50.)).end(),
Rectangle::new()
.color((0xFF, 0x00, 0x00).into())
.corner_radius(CornerRadius::All(5.))
.end(),
],
|c| {
c.with(
[
Id::new("test_text"),
Layout::new().end(),
Rectangle::new().color((0xFF, 0xFF, 0xFF).into()).end(),
// Text::new().color((0xFF, 0xFF, 0xFF).into()).end()
],
|_| {},
);
},
);
// Return the list of render commands of your layout
let render_commands = clay.end();
for command in render_commands {
// println!("Id of the element: {}", command.id); // Note: Ids are in fact numbers generated by Clay
// println!("Bounding box: {:?}", command.bounding_box);
// println!("Type and config: {:?}", command.config);
match command.config {
clay_layout::render_commands::RenderCommandConfig::Rectangle(rectangle) => {
dt.fill_rect(
command.bounding_box.x,
command.bounding_box.y,
command.bounding_box.width,
command.bounding_box.height,
&raqote::Source::Solid(SolidSource {
r: (rectangle.color.r * 255.0) as u8,
g: (rectangle.color.g * 255.0) as u8,
b: (rectangle.color.b * 255.0) as u8,
a: (rectangle.color.a * 255.0) as u8,
}),
&DrawOptions::new(),
);
}
clay_layout::render_commands::RenderCommandConfig::Border(border_container) => {
todo!()
}
clay_layout::render_commands::RenderCommandConfig::Text(_, text) => todo!(),
clay_layout::render_commands::RenderCommandConfig::Image(image) => todo!(),
clay_layout::render_commands::RenderCommandConfig::ScissorStart() => todo!(),
clay_layout::render_commands::RenderCommandConfig::ScissorEnd() => todo!(),
clay_layout::render_commands::RenderCommandConfig::Custom(custom) => todo!(),
clay_layout::render_commands::RenderCommandConfig::None() => {}
}
}
if window.is_key_down(minifb::Key::Escape) {
break;
}
let (width, height) = window.get_size();
if width as i32 != dt.width() || height as i32 != dt.height() {
dt = DrawTarget::new(width as i32, height as i32);
clay.layout_dimensions((width as f32, height as f32).into());
size = (width, height);
// window.update();
pub fn get(&mut self, size: i32) -> Option<Rc<Font>> {
let size = size.max(1);
if let Some(font) = self.fonts.get(&size) {
Some(font.clone())
} else {
window
.update_with_buffer(dt.get_data(), size.0, size.1)
.unwrap();
self.fonts_to_be_loaded.insert(size);
None
}
}
pub fn get_no_load(&self, size: i32) -> Option<Rc<Font>> {
if let Some(font) = self.fonts.get(&size) {
Some(font.clone())
} else {
None
}
}
pub fn load_fonts(
&mut self,
handle: &mut RaylibHandle,
thread: &RaylibThread,
) -> Result<(), String> {
for size in self.fonts_to_be_loaded.clone() {
assert!(size != 0, "HOW IS THIS ZERO?");
println!("Loading {} in {size}px", self.font);
let font = Rc::new(handle.load_font_ex(&thread, &self.font, size, None)?);
self.fonts.insert(size, font.clone());
}
self.fonts_to_be_loaded.clear();
Ok(())
}
pub fn text(
&mut self,
d: &mut RaylibDrawHandle,
text: &str,
x: f32,
y: f32,
size: f32,
letter_spacing: u16,
color: Option<Color>,
) {
// Program crashes at size=0.
let size = if size == 0. { 0.1 } else { size };
if let Some(font) = self.get(size as i32) {
d.draw_text_ex(
font.as_ref(),
text,
Vector2 { x, y },
size,
letter_spacing.into(),
color.unwrap_or(Color::BLUE),
);
}
}
}
#[inline]
fn clay2ray(c: clay_layout::color::Color) -> Color {
Color {
r: (c.r as f32 * 256.0) as u8,
g: (c.g as f32 * 256.0) as u8,
b: (c.b as f32 * 256.0) as u8,
a: (c.a as f32 * 256.0) as u8,
}
}
fn main() -> Result<(), String> {
println!("Openbirch GUI");
let (mut rl, thread) = raylib::init()
.size(640, 480)
.title("Openbirch GUI")
.msaa_4x()
.resizable()
.build();
let mut clay = clay_layout::Clay::new((640., 480.).into());
// let font = match rl.load_font_ex(&thread, "MononokiNerdFont-Regular.ttf", 20, None) {
// Ok(font) => font,
// Err(err) => panic!("Failed to load font {}", err),
// };
let font_store = Rc::new(RefCell::new(FontStore::new("MononokiNerdFont-Regular.ttf")));
let font_store_math = Rc::new(RefCell::new(FontStore::new("xits-italic.ttf")));
{
font_store.borrow_mut().get(16);
}
// stupid fucking 'static closures
let font_store_clone = font_store.clone();
let font_store_math_clone = font_store.clone();
clay.set_measure_text_function(move |text, config| {
let mut text_size = clay_layout::math::Dimensions::new(0., 0.);
let font_store = match config.font_id {
0 => font_store_clone.clone(),
1 => font_store_math_clone.clone(),
_ => panic!("Unknown font {}", config.font_id),
};
let font = if let Some(font) = font_store.borrow_mut().get(config.font_size.into()) {
font
} else {
println!("Font not cached. Returning: {:?}", text_size);
return text_size;
};
let size = font.measure_text(text, config.font_size.into(), config.letter_spacing.into());
text_size.width = size.x;
text_size.height = size.y;
println!("Measurements: {:?}", text_size);
text_size
});
rl.set_target_fps(30);
let mut debug = false;
while !rl.window_should_close() {
if rl.is_window_resized() {
let size = (rl.get_screen_width() as f32 - 200., rl.get_screen_height() as f32);
clay.layout_dimensions(size.into());
println!("Resized window to {:?}", size);
}
{
font_store.borrow_mut().load_fonts(&mut rl, &thread)?;
font_store_math.borrow_mut().load_fonts(&mut rl, &thread)?;
}
if rl.is_key_pressed(raylib::ffi::KeyboardKey::KEY_D) {
debug = !debug;
clay.reset_measure_text_cache();
clay.enable_debug_mode(debug);
println!("Toggled debug mode to {debug}");
}
clay.set_debug_highlight((0xff, 0x0, 0x0).into());
clay.begin();
clay.with(
Declaration::new()
.id(clay.id("body"))
.layout()
.width(grow!(0.))
.height(grow!(0.))
.direction(LayoutDirection::TopToBottom)
.padding(Padding::all(8))
.child_gap(0)
.end()
.scroll(false, true),
|clay| {
for i in 0..0 {
clay.with(
Declaration::new()
.id(clay.id(i.to_string().as_str()))
.layout()
.width(grow!(0.))
.height(fit!(0.))
.padding(Padding::all(4))
.end(),
|clay| {
clay.text(
"test",
TextConfig::new()
.color((0x0, 0x0, 0x0).into())
.font_size(16)
.end(),
)
},
);
}
},
);
let render_commands = clay.end();
let mut d = rl.begin_drawing(&thread);
d.clear_background(Color::WHITE);
/*
for (int j = 0; j < renderCommands.length; j++)
{
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
switch (renderCommand->commandType)
{
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
Clay_TextRenderData *textData = &renderCommand->renderData.text;
char *cloned = (char *)malloc(textData->stringContents.length + 1);
memcpy(cloned, textData->stringContents.chars, textData->stringContents.length);
cloned[textData->stringContents.length] = '\0';
Font fontToUse = fonts[textData->fontId];
DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
free(cloned);
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
Texture2D imageTexture = *(Texture2D *)renderCommand->renderData.image.imageData;
Clay_Color tintColor = renderCommand->renderData.image.backgroundColor;
if (tintColor.r == 0 && tintColor.g == 0 && tintColor.b == 0 && tintColor.a == 0) {
tintColor = (Clay_Color) { 255, 255, 255, 255 };
}
DrawTextureEx(
imageTexture,
(Vector2){boundingBox.x, boundingBox.y},
0,
boundingBox.width / (float)imageTexture.width,
CLAY_COLOR_TO_RAYLIB_COLOR(tintColor));
break;
}
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
break;
}
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
Clay_CustomRenderData *config = &renderCommand->renderData.custom;
CustomLayoutElement *customElement = (CustomLayoutElement *)config->customData;
if (!customElement) continue;
switch (customElement->type) {
case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: {
Clay_BoundingBox rootBox = renderCommands.internalArray[0].boundingBox;
float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f);
Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140);
BeginMode3D(Raylib_camera);
DrawModel(customElement->customData.model.model, positionRay.position, customElement->customData.model.scale * scaleValue, WHITE); // Draw 3d model with texture
EndMode3D();
break;
}
default: break;
}
break;
}
default: {
printf("Error: unhandled render command.");
exit(1);
}
}
}
*/
for command in render_commands {
match command.config {
RenderCommandConfig::Rectangle(rectangle) => {
println!("RECT!!! {}: {:?}\n{:?}\n\n", command.id, command.bounding_box, rectangle);
d.draw_rectangle_rounded(
raylib::ffi::Rectangle {
x: command.bounding_box.x,
y: command.bounding_box.y,
width: command.bounding_box.width,
height: command.bounding_box.height,
},
rectangle.corner_radii.top_left,
8,
clay2ray(rectangle.color),
);
}
RenderCommandConfig::Text(text) => {
// println!("{:?}", text);
font_store.borrow_mut().text(
&mut d,
text.text,
command.bounding_box.x,
command.bounding_box.y,
text.font_size as f32,
text.letter_spacing,
Some(clay2ray(text.color)),
);
}
RenderCommandConfig::ScissorStart() => unsafe {
raylib::ffi::BeginScissorMode(
command.bounding_box.x as i32,
command.bounding_box.y as i32,
command.bounding_box.width as i32,
command.bounding_box.height as i32,
);
},
RenderCommandConfig::ScissorEnd() => unsafe {
raylib::ffi::EndScissorMode();
},
RenderCommandConfig::Border(border) => {
// if (config->cornerRadius.topLeft > 0) {
// DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->width.top), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
// }
// if (config->cornerRadius.topRight > 0) {
// DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->width.top), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
// }
// if (config->cornerRadius.bottomLeft > 0) {
// DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->width.top), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
// }
// if (config->cornerRadius.bottomRight > 0) {
// DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->width.bottom), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
// }
// break;
// }
// Left border
if border.width.left > 0 {
d.draw_rectangle(
command.bounding_box.x as i32,
(command.bounding_box.y + border.corner_radii.top_left) as i32,
border.width.left as i32,
(command.bounding_box.height
- border.corner_radii.top_left
- border.corner_radii.bottom_left)
as i32,
clay2ray(border.color),
);
}
// Right border
if border.width.right > 0 {
d.draw_rectangle(
(command.bounding_box.x + command.bounding_box.width
- border.width.right as f32) as i32,
(command.bounding_box.y + border.corner_radii.top_right) as i32,
border.width.right as i32,
(command.bounding_box.height
- border.corner_radii.top_right
- border.corner_radii.bottom_right)
as i32,
clay2ray(border.color),
);
}
// Top border
if border.width.top > 0 {
d.draw_rectangle(
(command.bounding_box.x + border.corner_radii.top_left) as i32,
(command.bounding_box.y) as i32,
(command.bounding_box.width
- border.corner_radii.top_left
- border.corner_radii.top_right) as i32,
(border.width.top) as i32,
clay2ray(border.color),
);
}
// Top border
if border.width.bottom > 0 {
d.draw_rectangle(
(command.bounding_box.x + border.corner_radii.bottom_left) as i32,
(command.bounding_box.y + command.bounding_box.height
- border.width.bottom as f32) as i32,
(command.bounding_box.width
- border.corner_radii.bottom_left
- border.corner_radii.bottom_right)
as i32,
border.width.bottom as i32,
clay2ray(border.color),
);
}
}
RenderCommandConfig::None() => {}
_ => panic!("Unimplemented {:#?}", command),
}
}
}
Ok(())
}

BIN
xits-italic.otf Normal file

Binary file not shown.

BIN
xits-italic.ttf Normal file

Binary file not shown.