the ui is finally here

This commit is contained in:
Snorre Ettrup Altschul 2025-02-14 00:10:39 +01:00
parent 09eb8f134c
commit 6b2aa9fe14
8 changed files with 448 additions and 189 deletions

BIN
adamina.ttf Normal file

Binary file not shown.

BIN
adamina.woff Normal file

Binary file not shown.

View file

@ -4,22 +4,17 @@ use std::{
rc::Rc,
};
use clay_layout::{
Declaration,
bindings::Clay_ResetMeasureTextCache,
fit, grow,
id::Id,
layout::{LayoutDirection, Padding},
math::Dimensions,
render_commands::RenderCommandConfig,
text::{TextConfig, TextElementConfig},
};
mod gui;
use clay_layout::render_commands::RenderCommandConfig;
use gui::GUI;
use raylib::{
RaylibHandle, RaylibThread,
color::Color,
ffi::Vector2,
prelude::{Font, RaylibDraw, RaylibDrawHandle, RaylibScissorModeExt},
ffi::{MouseButton, TextLength, Vector2},
prelude::{Font, RaylibDraw, RaylibDrawHandle},
text::RaylibFont,
texture::Texture2D,
};
struct FontStore {
@ -29,9 +24,9 @@ struct FontStore {
}
impl FontStore {
const SIZE_PARAGRAPH: f32 = 20.;
const SIZE_HEADER: f32 = 30.;
const MOD_SUBTEXT: f32 = 0.5;
// const SIZE_PARAGRAPH: f32 = 20.;
// const SIZE_HEADER: f32 = 30.;
// const MOD_SUBTEXT: f32 = 0.5;
pub fn new(name: impl Into<String>) -> Self {
Self {
@ -51,27 +46,30 @@ impl FontStore {
}
}
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 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());
) -> Result<bool, String> {
let invalidate = self.fonts_to_be_loaded.len() > 0;
if invalidate {
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();
}
self.fonts_to_be_loaded.clear();
Ok(())
Ok(invalidate)
}
pub fn text(
@ -102,10 +100,10 @@ impl FontStore {
#[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,
r: c.r as u8,
g: c.g as u8,
b: c.b as u8,
a: c.a as u8,
}
}
@ -119,24 +117,24 @@ fn main() -> Result<(), String> {
.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 = Rc::new(RefCell::new(FontStore::new("adamina.ttf")));
let font_store_math = Rc::new(RefCell::new(FontStore::new("xits-italic.ttf")));
{
font_store.borrow_mut().get(16);
}
let mut gui = GUI::new();
let mut previous_left_button = false;
let mut previous_right_button = false;
// stupid fucking 'static closures
let font_store_clone = font_store.clone();
let font_store_math_clone = font_store.clone();
let font_store_math_clone = font_store_math.clone();
clay.set_measure_text_function(move |text, config| {
let mut text_size = clay_layout::math::Dimensions::new(0., 0.);
let mut max_text_width: f32 = 0.;
let mut line_text_width: f32 = 0.;
let font_store = match config.font_id {
0 => font_store_clone.clone(),
1 => font_store_math_clone.clone(),
@ -146,154 +144,110 @@ fn main() -> Result<(), String> {
let font = if let Some(font) = font_store.borrow_mut().get(config.font_size.into()) {
font
} else {
println!("Font not cached. Returning: {:?}", text_size);
// println!("Font not cached. Returning: {:?}", text_size);
return text_size;
};
let size = font.measure_text(text, config.font_size.into(), config.letter_spacing.into());
let scale_factor = config.font_size as f32 / font.baseSize as f32;
text_size.width = size.x;
text_size.height = size.y;
let text_height = config.font_size as f32;
println!("Measurements: {:?}", text_size);
for char in text.chars() {
if char == '\n' {
max_text_width = max_text_width.max(line_text_width);
line_text_width = 0.;
continue;
}
let idx = char as u32 - 32;
line_text_width += if let Some(glyph) = font.chars().get(idx as usize) {
if glyph.advanceX > 0 {
glyph.advanceX as f32
} else {
font.get_glyph_atlas_rec(char).width + glyph.offsetX as f32
}
} else {
0 as f32
} + config.letter_spacing as f32;
}
max_text_width = max_text_width.max(line_text_width);
text_size.width = max_text_width * scale_factor;
text_size.height = text_height;
text_size
// unsafe {
// std::mem::transmute(font.measure_text(text, config.font_size as f32, config.letter_spacing as f32 + 1.))
// }
});
rl.set_target_fps(30);
rl.set_target_fps(60);
rl.set_exit_key(None);
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);
let size = (rl.get_screen_width() as f32, rl.get_screen_height() as f32);
clay.layout_dimensions(size.into());
println!("Resized window to {:?}", size);
}
let left_button = rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT);
let right_button = rl.is_mouse_button_down(MouseButton::MOUSE_BUTTON_LEFT);
{
font_store.borrow_mut().load_fonts(&mut rl, &thread)?;
font_store_math.borrow_mut().load_fonts(&mut rl, &thread)?;
if font_store.borrow_mut().load_fonts(&mut rl, &thread)?
|| font_store_math.borrow_mut().load_fonts(&mut rl, &thread)?
{
clay.reset_measure_text_cache();
}
}
if rl.is_key_pressed(raylib::ffi::KeyboardKey::KEY_D) {
debug = !debug;
if rl.is_key_down(raylib::ffi::KeyboardKey::KEY_RIGHT_ALT)
&& rl.is_key_pressed(raylib::ffi::KeyboardKey::KEY_D)
{
debug = !clay.debug_mode_enabled();
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.set_debug_highlight((180, 142, 173, 150).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(),
)
},
);
}
},
clay.update_scroll_containers(
false,
unsafe { std::mem::transmute(rl.get_mouse_wheel_move_v()) },
rl.get_frame_time(),
);
let render_commands = clay.end();
clay.pointer_state(
unsafe { std::mem::transmute(rl.get_mouse_position()) },
left_button,
);
let render_commands = gui.render(
&clay,
(left_button, left_button && !previous_left_button),
(right_button, right_button && !previous_right_button),
(rl.get_char_pressed(), rl.get_key_pressed_number()),
rl.get_time()
);
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);
// println!(
// "RECT!!! {}: {:?}\n{:?}\n\n",
// command.id, command.bounding_box, rectangle
// );
//
d.draw_rectangle_rounded(
raylib::ffi::Rectangle {
x: command.bounding_box.x,
@ -307,8 +261,13 @@ fn main() -> Result<(), String> {
);
}
RenderCommandConfig::Text(text) => {
// println!("{:?}", text);
font_store.borrow_mut().text(
let font = match text.font_id {
0 => font_store.clone(),
1 => font_store_math.clone(),
_ => panic!("Unknown font {}", text.font_id),
};
font.borrow_mut().text(
&mut d,
text.text,
command.bounding_box.x,
@ -318,7 +277,6 @@ fn main() -> Result<(), String> {
Some(clay2ray(text.color)),
);
}
RenderCommandConfig::ScissorStart() => unsafe {
raylib::ffi::BeginScissorMode(
command.bounding_box.x as i32,
@ -330,22 +288,7 @@ fn main() -> Result<(), String> {
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(
@ -399,12 +342,95 @@ fn main() -> Result<(), String> {
clay2ray(border.color),
);
}
// Top left corner
if border.corner_radii.top_left > 0. {
d.draw_ring(
Vector2 {
x: command.bounding_box.x + border.corner_radii.top_left,
y: command.bounding_box.y + border.corner_radii.top_left,
},
border.corner_radii.top_left - border.width.top as f32,
border.corner_radii.top_left,
180.,
270.,
10,
clay2ray(border.color),
);
}
// Top right corner
if border.corner_radii.top_right > 0. {
d.draw_ring(
Vector2 {
x: command.bounding_box.x + command.bounding_box.width
- border.corner_radii.top_right,
y: command.bounding_box.y + border.corner_radii.top_right,
},
border.corner_radii.top_right - border.width.top as f32,
border.corner_radii.top_right,
270.,
360.,
10,
clay2ray(border.color),
);
}
// Bottom left
if border.corner_radii.bottom_left > 0. {
d.draw_ring(
Vector2 {
x: command.bounding_box.x + border.corner_radii.bottom_left,
y: command.bounding_box.y + command.bounding_box.height
- border.corner_radii.bottom_left,
},
border.corner_radii.bottom_left - border.width.top as f32,
border.corner_radii.bottom_left,
90.,
180.,
10,
clay2ray(border.color),
);
}
// Bottom right
if border.corner_radii.bottom_right > 0. {
d.draw_ring(
Vector2 {
x: command.bounding_box.x + command.bounding_box.width
- border.corner_radii.bottom_right,
y: command.bounding_box.y + command.bounding_box.height
- border.corner_radii.bottom_right,
},
border.corner_radii.bottom_right - border.width.bottom as f32,
border.corner_radii.bottom_right,
0.,
90.,
10,
clay2ray(border.color),
);
}
}
RenderCommandConfig::None() => {}
RenderCommandConfig::Image(image) => {
let texture: &Texture2D = unsafe {
std::mem::transmute::<*const std::ffi::c_void, *const Texture2D>(image.data)
.as_ref()
.unwrap()
};
d.draw_texture_ex(
texture,
Vector2 {
x: command.bounding_box.x,
y: command.bounding_box.y,
},
0.,
command.bounding_box.width / image.dimensions.width,
clay2ray(image.background_color),
);
}
_ => panic!("Unimplemented {:#?}", command),
}
}
previous_left_button = left_button;
previous_right_button = right_button;
}
Ok(())

224
src/app/gui/document.rs Normal file
View file

@ -0,0 +1,224 @@
use std::mem::ManuallyDrop;
use clay_layout::{
Clay, Declaration, fit, fixed, grow,
layout::{LayoutDirection, Padding},
text::TextConfig,
};
use raylib::ffi::KeyboardKey;
pub type LineType = String;
pub struct Document {
lines: Vec<(String, LineType)>,
total_lines: usize,
current_line: Option<usize>,
current_char: usize,
lines_to_delete: Vec<usize>,
lines_to_add: Vec<(usize, LineType)>,
last_type_time: f64,
}
impl Document {
pub fn new() -> Self {
Self {
lines: vec![("0".to_owned(), "".to_string())],
total_lines: 1,
current_line: Some(0),
current_char: 0,
lines_to_delete: vec![],
lines_to_add: vec![],
last_type_time: -100.,
}
}
pub fn add_line(&mut self, content: impl Into<LineType>) {
self.add_line_at(self.lines.len(), content);
}
pub fn add_line_at(&mut self, idx: usize, content: impl Into<LineType>) {
self.lines
.insert(idx, (self.total_lines.to_string(), content.into()));
self.total_lines += 1;
}
pub fn render<'a>(
&'a mut self,
clay: &'a Clay,
(left_held, left_pressed): (bool, bool),
(righ_held, right_pressed): (bool, bool),
(latest_char, latest_key): (Option<char>, Option<u32>),
time: f64,
) {
if !self.lines_to_delete.is_empty() {
self.lines_to_delete.sort();
for line in self.lines_to_delete.iter().rev() {
self.lines.remove(*line);
}
self.lines_to_delete.clear();
self.current_line = if let Some(i) = self.current_line {
if self.lines.len() == 0 {
self.add_line("".to_string());
}
Some(
(TryInto::<i32>::try_into(i).unwrap() - 1)
.max(0)
.min(TryInto::<i32>::try_into(self.lines.len()).unwrap() - 1)
.try_into()
.unwrap(),
)
} else {
None
};
}
if !self.lines_to_add.is_empty() {
// i am NOT cloning this shit
let lines_to_add: ManuallyDrop<Vec<(usize, String)>> = unsafe {
let len = self.lines_to_add.len();
let cap = self.lines_to_add.capacity();
let fuck = self.lines_to_add.as_mut_ptr();
std::mem::ManuallyDrop::new(Vec::from_raw_parts(fuck, len, cap))
};
for (idx, line) in lines_to_add.iter() {
self.add_line_at(*idx, line);
}
self.lines_to_add.clear();
self.current_line = if let Some(i) = self.current_line {
Some(i + 1)
} else {
None
};
}
if left_pressed && clay.pointer_over(clay.id("document")) {
self.current_line = None;
}
let len = self.lines.len();
let mut can_go_down = true;
clay.with(
&Declaration::new()
.id(clay.id("document"))
.layout()
.width(grow!(0.))
.height(grow!(0.))
.direction(LayoutDirection::TopToBottom)
.padding(Padding::all(8))
.child_gap(0)
.end()
.background_color((46, 52, 64).into())
.scroll(false, true),
|clay| {
for (i, line) in self.lines.iter_mut().enumerate() {
let current_selected =
if left_pressed && clay.pointer_over(clay.id(line.0.as_str())) {
self.current_line = Some(i);
true
} else {
Some(i) == self.current_line
};
if current_selected {
if let Some(char) = latest_char {
line.1 += char.to_string().as_str();
self.last_type_time = time;
}
match latest_key {
Some(val) if val == KeyboardKey::KEY_BACKSPACE as u32 => {
if line.1.len() == 0 {
self.lines_to_delete.push(i);
} else {
let _ = line.1.pop();
}
}
Some(val) if val == KeyboardKey::KEY_ENTER as u32 => {
self.lines_to_add.push((i + 1, "".to_string()));
}
Some(val) if val == KeyboardKey::KEY_UP as u32 => {
self.current_line = Some(
(TryInto::<i32>::try_into(self.current_line.unwrap()).unwrap()
- 1)
.max(0)
.try_into()
.unwrap(),
);
}
Some(val) if val == KeyboardKey::KEY_DOWN as u32 && can_go_down => {
self.current_line = Some(
(TryInto::<i32>::try_into(self.current_line.unwrap()).unwrap()
+ 1)
.min(TryInto::<i32>::try_into(len).unwrap() - 1)
.try_into()
.unwrap(),
);
can_go_down = false;
}
_ => {}
}
}
clay.with(
&Declaration::new()
.id(clay.id(line.0.as_str()))
.layout()
.width(grow!(0.))
.height(fit!(21.))
.direction(LayoutDirection::LeftToRight)
.padding(Padding::new(4, 4, 0, 1))
.child_alignment(clay_layout::layout::Alignment {
x: clay_layout::layout::LayoutAlignmentX::Left,
y: clay_layout::layout::LayoutAlignmentY::Center,
})
// .child_gap(2)
// .end()
// .border()
// .color((67, 76, 94).into())
// .bottom(2)
// .end()
// .corner_radius()
// .all(4.)
.end(),
|clay| {
clay.text(
line.1.as_str(),
TextConfig::new()
.color((229, 233, 240).into())
.font_size(20)
.font_id(0)
.letter_spacing(2)
.end(),
);
if current_selected
&& can_go_down
&& ((time - self.last_type_time) < 0.5 || time % 1.2 < 0.6)
{
clay.with(
&Declaration::new()
.id(clay.id("text_cursor"))
.layout()
.height(grow!(0.))
.width(fixed!(8.))
.end()
.background_color((229, 233, 240, 100).into())
// .corner_radius()
// .all(2.)
// .end(),
,
|_| {},
);
}
},
);
}
},
);
}
}

View file

@ -1,27 +1,36 @@
use std::rc::Rc;
use libopenbirch::node::{
NodeEnum, add::Add, constant::Constant, divide::Divide, multiply::Multiply, symbol::Symbol,
use clay_layout::{
Clay, Declaration, grow,
layout::{LayoutDirection, Padding},
render_commands::RenderCommand,
};
use math_segments::MathSegment;
use document::Document;
mod math_segments;
mod document;
pub struct GUI {
meth: Vec<Rc<dyn MathSegment>>,
pub document: Document,
}
impl GUI {
pub fn new() -> Self {
let a = Constant::new(69.0).into();
let b = Constant::new(420.0).into();
let c: NodeEnum = Add::new(a, b).into();
let d = Symbol::new_from_str("x").into();
let e = Divide::new(c.clone(), d).into();
let f = Multiply::new(e, c);
Self {
meth: vec![Rc::new(NodeEnum::Multiply(f))],
document: Document::new(),
}
}
pub fn render<'a>(
&'a mut self,
clay: &'a Clay,
left_mouse: (bool, bool),
right_mouse: (bool, bool),
latest_char: (Option<char>, Option<u32>),
time: f64,
) -> impl Iterator<Item = RenderCommand> {
clay.begin();
self.document
.render(clay, left_mouse, right_mouse, latest_char, time);
clay.end()
}
}

View file

@ -1,6 +1,6 @@
use std::rc::Rc;
use super::{Environment, Node, NodeEnum, Precedence, empty::Empty, symbol::Symbol};
use super::{Environment, Node, NodeEnum, Precedence, empty::Empty};
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Assign {

Binary file not shown.