-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
Hello Good day, i am working on a library based on eframe/egui to integrate GUI into my custom language, the aim i am trying to achieve is to create multi window ability where user can create a multiple form like form1, form2 and etc by calling createform just like winform. show_form is used for showing form which is equivalent to winform show(), runapp is use for starting the application which is equivalent to winform Application.run. Now when i tried to run this script:
import gui
function onclick()
gui.showform(form2)
end function
set form to gui.form("This is First", 500, 500)
set label to gui.label(form, "This is test", 200, 30, 200, 50, false)
gui.setbackcolor(label, "blue")
gui.setfontname(label, "Algerian")
gui.settextalignment(label, "center")
gui.setpadding(label, 20, 0, 0, 0)
set button to gui.button(form, "Show Form 2", onclick, 250, 80)
gui.setautosize(button, false)
gui.settextalignment(button, "center")
set form2 to gui.form("This is First", 500, 500)
//gui.showform(form2)
gui.runapp(form)if i click the button to it can show the form it will just freeze/hook and become unresponsive i.e the form1 which is the main entry, but i try that gui.showform that is outside onclick it will just popup and go down when the app started, i have tried many trial to solve the issue but can't find solution. Please help review and solve thank you, i have been on this thing for almost five days now. Below are code related to the form from the library
lazy_static::lazy_static! {
static ref COMMAND_QUEUE: Mutex<VecDeque<Command>> = Mutex::new(VecDeque::new());
static ref FORMS: RwLock<HashMap<String, FormSettings>> = RwLock::new(HashMap::new());
static ref CONTROLS: RwLock<HashMap<String, ControlSettings>> = RwLock::new(HashMap::new());
static ref EXIT_SENDER: RwLock<Option<Sender<()>>> = RwLock::new(None);
static ref TEXT_UPDATE_SENDERS: RwLock<HashMap<String, Sender<(String, String)>>> = RwLock::new(HashMap::new());
static ref MSGBOX_RESULT: RwLock<Option<(String, String)>> = RwLock::new(None);
}
#[derive(Clone, Debug)]
enum DockStyle {
None,
Top,
Bottom,
Left,
Right,
Fill,
}
#[derive(Clone, Debug)]
struct FormSettings {
title: String,
width: f32,
height: f32,
visible: bool,
bg_color: Color32,
maximized: bool,
fullscreen: bool,
startposition: String, // e.g., "centerscreen" or "manual"
resizable: bool,
position: Option<(f32, f32)>, // For manual positioning
border: bool,
controls_order: Vec<String>,
}
// Create Form
pub fn createform(args: Vec<Value>) -> Result<Value, String> {
if args.len() != 3 {
return Err(format!("createform() expects 3 arguments, got {}", args.len()));
}
let title = match &args[0] {
Value::String(s) => s.clone(),
_ => return Err("createform() expects a string for title".to_string()),
};
let width = match &args[1] {
Value::Number(n) => *n as f32,
_ => return Err("createform() expects a number for width".to_string()),
};
let height = match &args[2] {
Value::Number(n) => *n as f32,
_ => return Err("createform() expects a number for height".to_string()),
};
let form_id = Uuid::new_v4().to_string();
let settings = FormSettings {
title,
width,
height,
visible: false,
bg_color: Color32::from_rgb(230, 230, 230), // Light gray background
maximized: false,
fullscreen: false,
startposition: "centerscreen".to_string(),
resizable: true,
position: None,
border: true,
controls_order: Vec::new(),
};
println!("Created form {} with visible: {}", form_id, settings.visible);
FORMS.write().unwrap().insert(form_id.clone(), settings);
Ok(Value::FormObject(form_id))
}
pub fn runapp(args: Vec<Value>) -> Result<Value, String> {
if args.len() != 1 {
return Err(format!("runapp() expects 1 argument, got {}", args.len()));
}
let form_id = match &args[0] {
Value::FormObject(id) => id.clone(),
_ => return Err("runapp() expects a Form identifier".to_string()),
};
// Fix: Set the main form to visible
{
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&form_id) {
settings.visible = true; // Added to ensure the main form is visible
} else {
return Err("Form not found".to_string());
}
}
let (exit_tx, exit_rx) = channel();
{
let mut exit_sender = EXIT_SENDER.write().unwrap();
*exit_sender = Some(exit_tx);
}
let forms = FORMS.read().unwrap();
let form_settings = forms.get(&form_id)
.ok_or("Form not found".to_string())?
.clone();
let mut viewport = egui::ViewportBuilder::default()
.with_maximized(form_settings.maximized)
.with_fullscreen(form_settings.fullscreen)
.with_resizable(form_settings.resizable)
.with_decorations(form_settings.border);
if !form_settings.maximized && !form_settings.fullscreen {
viewport = viewport.with_inner_size([form_settings.width, form_settings.height]);
}
if form_settings.startposition == "manual" {
if let Some((x, y)) = form_settings.position {
viewport = viewport.with_position([x, y]);
}
}
let options = eframe::NativeOptions {
centered: true,
viewport,
..Default::default()
};
println!(
"Launching app - Maximized: {}, Fullscreen: {}, Border: {}, Size: {:?}",
form_settings.maximized,
form_settings.fullscreen,
form_settings.border,
if form_settings.maximized || form_settings.fullscreen {
"Max/Full".to_string()
} else {
format!("{}x{}", form_settings.width, form_settings.height)
}
);
eframe::run_native(
&form_settings.title,
options,
Box::new(|cc| Ok(Box::new(MyApp::new(form_id.clone(), exit_rx, cc)))),
)
.map_err(|e| format!("Failed to run app: {}", e))?;
{
let mut exit_sender = EXIT_SENDER.write().unwrap();
*exit_sender = None;
}
Ok(Value::Null)
}The App
struct MyApp {
form_id: String,
exit_rx: Arc<Mutex<Receiver<()>>>,
textbox_texts: HashMap<String, String>,
text_update_rx: Receiver<(String, String)>,
shown_viewports: HashSet<String>,
}
impl MyApp {
fn new(form_id: String, exit_rx: Receiver<()>, cc: &eframe::CreationContext<'_>) -> Self {
// Define custom fonts
let mut fonts = FontDefinitions::default();
let exe_path = std::env::current_exe().expect("Failed to get executable path");
let exe_dir = exe_path.parent().expect("Failed to get executable directory");
let fonts_dir = exe_dir.join("fonts");
// List of font files to load from the 'fonts' subfolder
let font_files = [
("SegoeUI", "SegoeUI.ttf"),
("SegoeUIBold", "SegoeUIBold.ttf"),
("SegoeUIItalic", "SegoeUIItalic.ttf"),
("SegoeUIBoldItalic", "SegoeUIBoldItalic.ttf"),
("SansSerif", "SansSerif.otf"),
("Algerian", "algerian.ttf")
];
for (font_name, file_name) in font_files.iter() {
let font_path = fonts_dir.join(file_name);
if font_path.exists() {
match fs::read(&font_path) {
Ok(font_data) => {
fonts.font_data.insert(
font_name.to_string(),
FontData::from_owned(font_data).into(),
);
fonts.families.insert(
FontFamily::Name(font_name.to_string().into()),
vec![font_name.to_string()],
);
}
Err(e) => eprintln!("Failed to load font {}: {}", font_path.display(), e),
}
} else {
eprintln!("Font file not found: {}", font_path.display());
}
}
// Set the fonts in the egui context
cc.egui_ctx.set_fonts(fonts);
let (text_update_tx, text_update_rx) = channel();
TEXT_UPDATE_SENDERS.write().unwrap().insert(form_id.clone(), text_update_tx);
Self {
form_id,
exit_rx: Arc::new(Mutex::new(exit_rx)),
textbox_texts: HashMap::new(),
text_update_rx,
shown_viewports: HashSet::new(),
}
}
}Update function
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Check if the application should exit (Receiving Signal)
if self.exit_rx.lock().unwrap().try_recv().is_ok() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
return;
}
// Clone the forms data and release the lock immediately
let forms_data = {
let forms = FORMS.read().unwrap(); // Acquire read lock
forms.clone() // Clone the data
}; // Lock is released here
// Render the main form if it’s visible
if let Some(settings) = forms_data.get(&self.form_id) {
if settings.visible {
egui::CentralPanel::default().show(ctx, |ui| {
// Autosize and constraint logic
let (autosize_controls, constraint_controls): (Vec<_>, Vec<_>) = {
let controls = CONTROLS.read().unwrap();
let autosize = controls.iter()
.filter(|(_, c)| c.form_id == self.form_id && c.autosize)
.map(|(id, c)| (id.clone(), c.text.clone(), c.fontname.clone(), c.fontsize, c.forecolor, c.padding))
.collect();
let constraints = controls.iter()
.filter(|(_, c)| c.form_id == self.form_id && c.layout_constraint.is_some())
.map(|(id, c)| (id.clone(), c.layout_constraint.clone().unwrap()))
.collect();
(autosize, constraints)
};
let autosize_sizes: HashMap<String, (f32, f32)> = autosize_controls.into_iter()
.map(|(id, text, fontname, fontsize, forecolor, padding)| {
let control = ControlSettings {
fontname,
fontsize,
forecolor,
padding,
..Default::default()
};
let (text_width, text_height) = ui_text_size(ui, &text, &control);
let total_width = text_width + padding.0 + padding.2;
let total_height = text_height + padding.1 + padding.3;
(id, (total_width, total_height))
})
.collect();
{
let mut controls = CONTROLS.write().unwrap();
for (id, (total_width, total_height)) in &autosize_sizes {
if let Some(control) = controls.get_mut(id) {
control.width = *total_width;
control.height = *total_height;
}
}
let target_data: HashMap<String, (egui::Pos2, f32, f32)> = constraint_controls.iter()
.filter_map(|(_, constraint)| match constraint {
LayoutConstraint::LeftOf { target_id, .. } |
LayoutConstraint::RightOf { target_id, .. } |
LayoutConstraint::Above { target_id, .. } |
LayoutConstraint::Below { target_id, .. } => {
controls.get(target_id).map(|target| {
(target_id.clone(), (target.position, target.width, target.height))
})
}
})
.collect();
for (id, constraint) in &constraint_controls {
if let Some(control) = controls.get_mut(id) {
match constraint {
LayoutConstraint::LeftOf { target_id, space } => {
if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
control.position.x = target_pos.x - control.width - *space;
control.position.y = target_pos.y;
}
}
LayoutConstraint::RightOf { target_id, space } => {
if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
control.position.x = target_pos.x + *target_width + *space;
control.position.y = target_pos.y;
}
}
LayoutConstraint::Above { target_id, space } => {
if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
control.position.x = target_pos.x;
control.position.y = target_pos.y - control.height - *space;
}
}
LayoutConstraint::Below { target_id, space } => {
if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
control.position.x = target_pos.x;
control.position.y = target_pos.y + *target_height + *space;
}
}
}
}
}
}
// Gather visible controls for rendering
let controls_data = {
let controls = CONTROLS.read().unwrap();
controls.iter()
.filter(|(_, c)| c.form_id == self.form_id && c.visible)
.map(|(id, c)| (id.clone(), c.clone()))
.collect::<Vec<_>>()
};
let mut textbox_updates = Vec::new();
let form_rect = ui.available_rect_before_wrap();
ui.painter().rect_filled(form_rect, 0.0, settings.bg_color);
// Render controls
for (control_id, control) in controls_data.iter() {
let mut pos;
let mut size;
if matches!(control.dock, DockStyle::None) {
pos = control.position + egui::Vec2::new(control.margin.0, control.margin.1);
size = egui::Vec2::new(control.width, control.height);
} else {
let (text_width, text_height) = if control.autosize {
ui_text_size(ui, &control.text, control)
} else {
(0.0, 0.0)
};
match control.dock {
DockStyle::Top => {
let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
pos = form_rect.min;
size = egui::Vec2::new(form_rect.width(), height);
}
DockStyle::Bottom => {
let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
pos = egui::pos2(form_rect.min.x, form_rect.max.y - height);
size = egui::Vec2::new(form_rect.width(), height);
}
DockStyle::Left => {
let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
pos = form_rect.min;
size = egui::Vec2::new(width, form_rect.height());
}
DockStyle::Right => {
let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
pos = egui::pos2(form_rect.max.x - width, form_rect.min.y);
size = egui::Vec2::new(width, form_rect.height());
}
DockStyle::Fill => {
pos = form_rect.min;
size = form_rect.size();
}
DockStyle::None => unreachable!(),
}
}
let rect = egui::Rect::from_min_size(pos, size);
match control.control_type.as_str() {
"label" => {
ui.painter().rect_filled(rect, 0.0, control.backcolor);
let inner_rect = egui::Rect {
min: rect.min + egui::Vec2::new(control.padding.0, control.padding.1),
max: rect.max - egui::Vec2::new(control.padding.2, control.padding.3),
};
let font_family = get_font_family(&control.fontname);
let font_id = FontId::new(control.fontsize, font_family);
let mut job = LayoutJob::default();
job.append(
&control.text,
0.0,
TextFormat {
font_id,
color: control.forecolor,
..Default::default()
},
);
let galley = ui.fonts(|f| f.layout_job(job));
let text_size = galley.rect.size();
let align_x = control.text_alignment[0];
let align_y = control.text_alignment[1];
let offset_x = match align_x {
Align::Min => 0.0,
Align::Center => (inner_rect.width() - text_size.x) / 2.0,
Align::Max => inner_rect.width() - text_size.x,
};
let offset_y = match align_y {
Align::Min => 0.0,
Align::Center => (inner_rect.height() - text_size.y) / 2.0,
Align::Max => inner_rect.height() - text_size.y,
};
let text_pos = inner_rect.min + egui::Vec2::new(offset_x, offset_y);
ui.painter().galley(text_pos, galley, control.forecolor);
}
"button" => {
// Updated button rendering with text alignment and padding
let mut bg_color = control.backcolor;
let response = ui.interact(rect, ui.next_auto_id(), Sense::click());
if response.hovered() {
bg_color = Color32::from_rgb(
(bg_color.r() as f32 * 1.1).min(255.0) as u8,
(bg_color.g() as f32 * 1.1).min(255.0) as u8,
(bg_color.b() as f32 * 1.1).min(255.0) as u8,
);
}
if response.clicked() {
bg_color = Color32::from_rgb(
(bg_color.r() as f32 * 0.9).max(0.0) as u8,
(bg_color.g() as f32 * 0.9).max(0.0) as u8,
(bg_color.b() as f32 * 0.9).max(0.0) as u8,
);
}
ui.painter().rect_filled(rect, 0.0, bg_color);
let inner_rect = Rect {
min: rect.min + Vec2::new(control.padding.0, control.padding.1),
max: rect.max - Vec2::new(control.padding.2, control.padding.3),
};
let font_id = FontId::new(control.fontsize, get_font_family(&control.fontname));
let mut job = LayoutJob::default();
job.append(&control.text, 0.0, TextFormat {
font_id,
color: control.forecolor,
..Default::default()
});
let galley = ui.fonts(|f| f.layout_job(job));
let text_size = galley.rect.size();
let align_x = control.text_alignment[0];
let align_y = control.text_alignment[1];
let offset_x = match align_x {
Align::Min => 0.0,
Align::Center => (inner_rect.width() - text_size.x) / 2.0,
Align::Max => inner_rect.width() - text_size.x,
};
let offset_y = match align_y {
Align::Min => 0.0,
Align::Center => (inner_rect.height() - text_size.y) / 2.0,
Align::Max => inner_rect.height() - text_size.y,
};
let text_pos = inner_rect.min + Vec2::new(offset_x, offset_y);
ui.painter().galley(text_pos, galley, control.forecolor);
if response.clicked() {
if let Some(callback) = &control.callback {
match callback {
Value::Function { name, params, body, closure, object } => {
let mut interpreter = GLOBAL_INTERPRETER.lock().unwrap();
let mut local_env = Arc::new(Mutex::new(
Environment::new(Some(closure.clone()))
));
if !params.is_empty() {
}
if let Some(obj) = object {
let value = obj.lock().unwrap().clone();
local_env.lock().unwrap().define("this".to_string(), value);
}
let _ = interpreter.visit_block(body, &mut local_env);
}
Value::String(s) => {
match s.as_str() {
"close_msgbox" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
"close_msgbox_yes" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
"close_msgbox_no" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
"close_msgbox_cancel" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
_ => {}
}
}
_ => {}
}
}
}
if response.hovered() && !control.cursor.is_empty() {
let cursor_icon = match control.cursor.to_lowercase().as_str() {
"hand" | "pointer" => CursorIcon::PointingHand,
"default" => CursorIcon::Default,
"crosshair" => CursorIcon::Crosshair,
"text" => CursorIcon::Text,
"move" => CursorIcon::Move,
"grab" => CursorIcon::Grab,
"grabbing" => CursorIcon::Grabbing,
_ => CursorIcon::Default,
};
ui.output_mut(|o| o.cursor_icon = cursor_icon);
}
}
"textbox" => {
let text = self.textbox_texts.entry(control_id.clone()).or_insert_with(|| control.text.clone());
ui.painter().rect_filled(rect, 0.0, control.backcolor);
let response = ui.allocate_ui_at_rect(rect, |ui| {
let font_family = get_font_family(&control.fontname);
let font_id = FontId::new(control.fontsize, font_family);
if control.multiline {
egui::ScrollArea::vertical().show(ui, |ui| {
let text_edit = egui::TextEdit::multiline(text)
.text_color(control.forecolor)
.font(font_id)
.frame(false)
.desired_width(control.width);
ui.add_sized(size, text_edit)
}).inner
} else {
let text_edit = egui::TextEdit::singleline(text)
.text_color(control.forecolor)
.font(font_id)
.frame(false)
.desired_width(control.width);
ui.add_sized(size, text_edit)
}
});
if response.inner.changed() {
textbox_updates.push((control_id.clone(), text.clone()));
}
}
_ => {
ui.painter().rect_filled(rect, 0.0, Color32::RED);
}
}
}
if !textbox_updates.is_empty() {
let mut controls = CONTROLS.write().unwrap();
for (control_id, new_text) in textbox_updates {
if let Some(control) = controls.get_mut(&control_id) {
control.text = new_text;
}
}
}
});
}
}
// Clean up shown_viewports by removing hidden forms
self.shown_viewports.retain(|form_id| {
forms_data.get(form_id).map_or(false, |settings| settings.visible)
});
// Schedule additional viewports for visible forms
for (form_id, settings) in forms_data.iter() {
if settings.visible && form_id != &self.form_id {
if !self.shown_viewports.contains(form_id) {
self.shown_viewports.insert(form_id.clone());
let form_id_clone = form_id.clone();
let settings_clone = settings.clone();
let textbox_texts_clone = std::sync::Mutex::new(self.textbox_texts.clone());
ctx.show_viewport_deferred(
egui::ViewportId::from_hash_of(form_id_clone.clone()),
egui::ViewportBuilder::default()
.with_title(&settings_clone.title)
.with_inner_size([settings_clone.width, settings_clone.height])
.with_maximized(settings_clone.maximized)
.with_fullscreen(settings_clone.fullscreen)
.with_resizable(settings_clone.resizable)
.with_decorations(settings_clone.border),
move |ctx, _class| {
let forms = FORMS.read().unwrap();
if let Some(settings) = forms.get(&form_id_clone) {
if !settings.visible {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
return;
}
if ctx.input(|i| i.viewport().close_requested()) {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&form_id_clone) {
settings.visible = false;
}
} else {
egui::CentralPanel::default().show(ctx, |ui| {
let (autosize_controls, constraint_controls): (Vec<_>, Vec<_>) = {
let controls = CONTROLS.read().unwrap();
let autosize = controls.iter()
.filter(|(_, c)| c.form_id == form_id_clone && c.autosize)
.map(|(id, c)| (id.clone(), c.text.clone(), c.fontname.clone(), c.fontsize, c.forecolor, c.padding))
.collect();
let constraints = controls.iter()
.filter(|(_, c)| c.form_id == form_id_clone && c.layout_constraint.is_some())
.map(|(id, c)| (id.clone(), c.layout_constraint.clone().unwrap()))
.collect();
(autosize, constraints)
};
let autosize_sizes: HashMap<String, (f32, f32)> = autosize_controls.into_iter()
.map(|(id, text, fontname, fontsize, forecolor, padding)| {
let control = ControlSettings {
fontname,
fontsize,
forecolor,
padding,
..Default::default()
};
let (text_width, text_height) = ui_text_size(ui, &text, &control);
let total_width = text_width + padding.0 + padding.2;
let total_height = text_height + padding.1 + padding.3;
(id, (total_width, total_height))
})
.collect();
{
let mut controls = CONTROLS.write().unwrap();
for (id, (total_width, total_height)) in &autosize_sizes {
if let Some(control) = controls.get_mut(id) {
control.width = *total_width;
control.height = *total_height;
}
}
let target_data: HashMap<String, (egui::Pos2, f32, f32)> = constraint_controls.iter()
.filter_map(|(_, constraint)| match constraint {
LayoutConstraint::LeftOf { target_id, .. } |
LayoutConstraint::RightOf { target_id, .. } |
LayoutConstraint::Above { target_id, .. } |
LayoutConstraint::Below { target_id, .. } => {
controls.get(target_id).map(|target| {
(target_id.clone(), (target.position, target.width, target.height))
})
}
})
.collect();
for (id, constraint) in &constraint_controls {
if let Some(control) = controls.get_mut(id) {
match constraint {
LayoutConstraint::LeftOf { target_id, space } => {
if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
control.position.x = target_pos.x - control.width - *space;
control.position.y = target_pos.y;
}
}
LayoutConstraint::RightOf { target_id, space } => {
if let Some((target_pos, target_width, _)) = target_data.get(target_id) {
control.position.x = target_pos.x + *target_width + *space;
control.position.y = target_pos.y;
}
}
LayoutConstraint::Above { target_id, space } => {
if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
control.position.x = target_pos.x;
control.position.y = target_pos.y - control.height - *space;
}
}
LayoutConstraint::Below { target_id, space } => {
if let Some((target_pos, _, target_height)) = target_data.get(target_id) {
control.position.x = target_pos.x;
control.position.y = target_pos.y + *target_height + *space;
}
}
}
}
}
}
let controls_data = {
let controls = CONTROLS.read().unwrap();
controls.iter()
.filter(|(_, c)| c.form_id == form_id_clone && c.visible)
.map(|(id, c)| (id.clone(), c.clone()))
.collect::<Vec<_>>()
};
let mut textbox_updates = Vec::new();
let form_rect = ui.available_rect_before_wrap();
ui.painter().rect_filled(form_rect, 0.0, settings.bg_color);
for (control_id, control) in controls_data.iter() {
let mut pos;
let mut size;
if matches!(control.dock, DockStyle::None) {
pos = control.position + Vec2::new(control.margin.0, control.margin.1);
size = Vec2::new(control.width, control.height);
} else {
let (text_width, text_height) = if control.autosize {
ui_text_size(ui, &control.text, control)
} else {
(0.0, 0.0)
};
match control.dock {
DockStyle::Top => {
let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
pos = form_rect.min;
size = Vec2::new(form_rect.width(), height);
}
DockStyle::Bottom => {
let height = if control.autosize { text_height + control.padding.1 + control.padding.3 } else { control.height };
pos = egui::pos2(form_rect.min.x, form_rect.max.y - height);
size = Vec2::new(form_rect.width(), height);
}
DockStyle::Left => {
let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
pos = form_rect.min;
size = Vec2::new(width, form_rect.height());
}
DockStyle::Right => {
let width = if control.autosize { text_width + control.padding.0 + control.padding.2 } else { control.width };
pos = egui::pos2(form_rect.max.x - width, form_rect.min.y);
size = Vec2::new(width, form_rect.height());
}
DockStyle::Fill => {
pos = form_rect.min;
size = form_rect.size();
}
DockStyle::None => unreachable!(),
}
}
let rect = Rect::from_min_size(pos, size);
match control.control_type.as_str() {
"label" => {
ui.painter().rect_filled(rect, 0.0, control.backcolor);
let inner_rect = Rect {
min: rect.min + Vec2::new(control.padding.0, control.padding.1),
max: rect.max - Vec2::new(control.padding.2, control.padding.3),
};
let font_family = get_font_family(&control.fontname);
let font_id = FontId::new(control.fontsize, font_family);
let mut job = LayoutJob::default();
job.append(
&control.text,
0.0,
TextFormat {
font_id,
color: control.forecolor,
..Default::default()
},
);
let galley = ui.fonts(|f| f.layout_job(job));
let text_size = galley.rect.size();
let align_x = control.text_alignment[0];
let align_y = control.text_alignment[1];
let offset_x = match align_x {
Align::Min => 0.0,
Align::Center => (inner_rect.width() - text_size.x) / 2.0,
Align::Max => inner_rect.width() - text_size.x,
};
let offset_y = match align_y {
Align::Min => 0.0,
Align::Center => (inner_rect.height() - text_size.y) / 2.0,
Align::Max => inner_rect.height() - text_size.y,
};
let text_pos = inner_rect.min + Vec2::new(offset_x, offset_y);
ui.painter().galley(text_pos, galley, control.forecolor);
}
"button" => {
let mut bg_color = control.backcolor;
let response = ui.interact(rect, ui.next_auto_id(), Sense::click());
if response.hovered() {
bg_color = Color32::from_rgb(
(bg_color.r() as f32 * 1.1).min(255.0) as u8,
(bg_color.g() as f32 * 1.1).min(255.0) as u8,
(bg_color.b() as f32 * 1.1).min(255.0) as u8,
);
}
if response.clicked() {
bg_color = Color32::from_rgb(
(bg_color.r() as f32 * 0.9).max(0.0) as u8,
(bg_color.g() as f32 * 0.9).max(0.0) as u8,
(bg_color.b() as f32 * 0.9).max(0.0) as u8,
);
}
ui.painter().rect_filled(rect, 0.0, bg_color);
let inner_rect = Rect {
min: rect.min + Vec2::new(control.padding.0, control.padding.1),
max: rect.max - Vec2::new(control.padding.2, control.padding.3),
};
let font_id = FontId::new(control.fontsize, get_font_family(&control.fontname));
let mut job = LayoutJob::default();
job.append(&control.text, 0.0, TextFormat {
font_id,
color: control.forecolor,
..Default::default()
});
let galley = ui.fonts(|f| f.layout_job(job));
let text_size = galley.rect.size();
let align_x = control.text_alignment[0];
let align_y = control.text_alignment[1];
let offset_x = match align_x {
Align::Min => 0.0,
Align::Center => (inner_rect.width() - text_size.x) / 2.0,
Align::Max => inner_rect.width() - text_size.x,
};
let offset_y = match align_y {
Align::Min => 0.0,
Align::Center => (inner_rect.height() - text_size.y) / 2.0,
Align::Max => inner_rect.height() - text_size.y,
};
let text_pos = inner_rect.min + Vec2::new(offset_x, offset_y);
ui.painter().galley(text_pos, galley, control.forecolor);
if response.clicked() {
if let Some(callback) = &control.callback {
match callback {
Value::Function { name, params, body, closure, object } => {
let mut interpreter = GLOBAL_INTERPRETER.lock().unwrap();
let mut local_env = Arc::new(Mutex::new(
Environment::new(Some(closure.clone()))
));
if !params.is_empty() {
}
if let Some(obj) = object {
let value = obj.lock().unwrap().clone();
local_env.lock().unwrap().define("this".to_string(), value);
}
let _ = interpreter.visit_block(body, &mut local_env);
}
Value::String(s) => {
match s.as_str() {
"close_msgbox" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
"close_msgbox_yes" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
"close_msgbox_no" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
"close_msgbox_cancel" => {
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&control.form_id) {
settings.visible = false;
}
}
_ => {}
}
}
_ => {}
}
}
}
if response.hovered() && !control.cursor.is_empty() {
let cursor_icon = match control.cursor.to_lowercase().as_str() {
"hand" | "pointer" => CursorIcon::PointingHand,
"default" => CursorIcon::Default,
"crosshair" => CursorIcon::Crosshair,
"text" => CursorIcon::Text,
"move" => CursorIcon::Move,
"grab" => CursorIcon::Grab,
"grabbing" => CursorIcon::Grabbing,
_ => CursorIcon::Default,
};
ui.output_mut(|o| o.cursor_icon = cursor_icon);
}
}
"textbox" => {
let mut textbox_texts = textbox_texts_clone.lock().unwrap();
let text = textbox_texts.entry(control_id.clone()).or_insert_with(|| control.text.clone());
ui.painter().rect_filled(rect, 0.0, control.backcolor);
let response = ui.allocate_ui_at_rect(rect, |ui| {
let font_family = get_font_family(&control.fontname);
let font_id = FontId::new(control.fontsize, font_family);
if control.multiline {
egui::ScrollArea::vertical().show(ui, |ui| {
let text_edit = egui::TextEdit::multiline(text)
.text_color(control.forecolor)
.font(font_id)
.frame(false)
.desired_width(control.width);
ui.add_sized(size, text_edit)
}).inner
} else {
let text_edit = egui::TextEdit::singleline(text)
.text_color(control.forecolor)
.font(font_id)
.frame(false)
.desired_width(control.width);
ui.add_sized(size, text_edit)
}
});
if response.inner.changed() {
textbox_updates.push((control_id.clone(), text.clone()));
}
}
_ => {
ui.painter().rect_filled(rect, 0.0, Color32::RED);
}
}
}
if !textbox_updates.is_empty() {
let mut controls = CONTROLS.write().unwrap();
for (control_id, new_text) in textbox_updates {
if let Some(control) = controls.get_mut(&control_id) {
control.text = new_text;
}
}
}
});
}
}
},
);
}
}
}
}
}Show form code
pub fn show_form(args: Vec<Value>) -> Result<Value, String> {
if args.len() != 1 {
return Err(format!("show_form() expects 1 argument, got {}", args.len()));
}
let form_id = match &args[0] {
Value::FormObject(id) => id.clone(),
_ => return Err("show_form() expects a Form identifier".to_string()),
};
let mut forms = FORMS.write().unwrap();
if let Some(settings) = forms.get_mut(&form_id) {
settings.visible = true;
println!("Form {} set to visible", form_id);
Ok(Value::Null)
} else {
Err("Form not found".to_string())
}
}