Skip to content

Commit

Permalink
Made it possible to right click, left click, and hover
Browse files Browse the repository at this point in the history
  • Loading branch information
uek-1 committed Jul 3, 2023
1 parent e6894dc commit 8f410bb
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 75 deletions.
6 changes: 6 additions & 0 deletions model-gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ impl App {
pub fn new(cc : &eframe::CreationContext<'_>) -> Self {
let visuals = egui::Visuals::dark();
cc.egui_ctx.set_visuals(visuals);
let mut debug_opt = egui::style::DebugOptions::default();
debug_opt.debug_on_hover = true;
debug_opt.show_interactive_widgets = true;
let mut style = (*cc.egui_ctx.style()).clone();
style.debug = debug_opt;
cc.egui_ctx.set_style(style);

App {
time: Instant::now(),
Expand Down
52 changes: 44 additions & 8 deletions model-gui/src/widgets/layer.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,71 @@
use egui::{Widget, Area, Rect, Sense, Pos2, Vec2, RichText};
use egui::{Widget, Area, Rect, Sense, Pos2, Vec2, RichText, LayerId, Order};
use std::fmt::Write;
use super::neuron::{neuron};

pub fn layer_ui(ui: &mut egui::Ui, layer : &mut rust_mlp::Layer<f64>, radius : f32) -> egui::Response {
let layer_resp = ui.vertical( |ui| {
let neuron_size = Vec2::new(2.5 * radius, 4.0 * radius); // 4x radius for 1 neuron padding
let layer_info = format!("Layer: Neurons: {}, Dimensions: {} x {}, Activation {:?}", layer.weights.len(), layer.weights[0].len(), layer.weights.len(), layer.activation);
let neuron_size = Vec2::new(2.0 * radius, 2.0 * radius); // 4x radius for 1 neuron padding
let top_left = ui.min_rect().min + Vec2::new(-1.25 * radius, -4.0 * radius * layer.weights.len() as f32);
let bottom_right = ui.min_rect().min + Vec2::new(1.25 * radius + 15.0, 2.5 * radius * layer.weights.len() as f32);
let layer_rect = Rect::from_min_max(top_left, bottom_right);
let return_resp = ui.interact(layer_rect, ui.next_auto_id() ,Sense::click());

ui.vertical(|ui| {
layer.weights
.iter_mut()
.enumerate()
.for_each(
|(num, weights)| {
ui.add_sized(neuron_size, neuron(radius, weights));
let response = ui.add_sized(neuron_size, neuron(radius, weights));
handle_neuron_response(ui, response.clone(), weights.to_vec());
ui.add_space(2.0 * radius);
}
);
}).response;

});

ui.add_space(15.0);
let layer_info = format!("Layer: Neurons: {}, Dimensions: {} x {}, Activation {:?}", layer.weights.len(), layer.weights[0].len(), layer.weights.len(), layer.activation);
ui
.interact(ui.min_rect(), ui.next_auto_id(), Sense::hover())
.on_hover_ui_at_pointer(|ui| {
ui.label(RichText::new(layer_info).size(16.0));
});

// Return click response to handle right click context menu in model widget
ui.interact(ui.min_rect(), ui.next_auto_id(), Sense::click())
return_resp
}

#[allow(unused)]
fn handle_neuron_response(ui: &mut egui::Ui, response: egui::Response, weights: Vec<f64>) {
if response.clicked() {
response.request_focus();
};

if response.has_focus() {
let mut weights_string = String::from("Weights: [");
for weight in weights {
write!(weights_string, "{:.3}, ", weight);
}
write!(weights_string, "]");

let area = Area::new(response.id)
.order(egui::Order::Foreground)
.constrain(true)
.fixed_pos(response.rect.min)
.interactable(true);

area.show(&response.ctx, |ui| {
let frame = egui::Frame::menu(ui.style()).show(ui, |ui| {
const DEFAULT_MENU_WIDTH: f32 = 150.0;
ui.set_max_width(DEFAULT_MENU_WIDTH);
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
ui.label(RichText::new(weights_string).size(10.0));
}).inner
});
});
}

//layer_resp.on_hover_text_at_pointer(layer_info)
}

pub fn layer(layer: &mut rust_mlp::Layer<f64>, radius: f32) -> impl Widget +'_{
Expand Down
117 changes: 66 additions & 51 deletions model-gui/src/widgets/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,71 +14,86 @@ impl LayerState {
}
}

pub struct LayerMenuState {
index : usize,
input_shape: usize,
last_layer_index: Option<usize>,
layer_to_delete: Option<usize>,
layer_to_add: Option<(usize, rust_mlp::Layer<f64>)>
}

pub fn model<'a>(model: &'a mut Model<f64>, layer_state: &'a mut LayerState) -> impl egui::Widget + 'a {
move |ui : &mut egui::Ui| model_ui(ui, model, layer_state)
}

pub fn model_ui(ui: &mut egui::Ui, model: &mut Model<f64>, layer_state : &mut LayerState) -> egui::Response {
let radius = 30.0;
let mut layer_to_delete = None;
let mut layer_to_add = None;
let last_layer_num = model.layers.len().checked_sub(1);
let return_response = ui.horizontal_centered(
|ui| {
let mut current_pos = ui.next_widget_position();

model.layers
.iter_mut()
.enumerate()
.for_each(
|(num, model_layer)| {
let layer_neurons = model_layer.weights.len() as f32;
let layer_size = Vec2::new(radius * 2.5, radius * 4.0 * layer_neurons);
let layer_rect = Rect::from_min_size(current_pos, layer_size);
ui.add_sized(layer_size, layer(model_layer, radius))
.context_menu(|ui| {
ui.menu_button("New", |ui| {
ui.text_edit_singleline(&mut layer_state.neuron_count_string).on_hover_text("Enter the number of neurons");
egui::ComboBox::from_label("Activation Function")
.selected_text(format!("{:?}", layer_state.activation))
.show_ui(ui, |ui| {
ui.selectable_value(&mut layer_state.activation, Activation::Sigmoid, "Sigmoid");
ui.selectable_value(&mut layer_state.activation, Activation::None, "None");
});

if ui.button("Add").clicked() {
let input_shape = layer_neurons as usize;
let output_shape = layer_state.neuron_count_string.clone().parse().unwrap();
layer_to_add = Some((num, rust_mlp::Layer::from_size(input_shape, output_shape, layer_state.activation.clone())));
ui.close_menu();
};
});

// Safety : Can unwrap safely because layers > 1 in this code block
// so last_layer_num must exist.

if num != last_layer_num.unwrap() && ui.button("Delete").clicked() {
layer_to_delete = Some(num);
ui.close_menu();
}
});
}
);
}
).response;

match (layer_to_delete, last_layer_num) {
let mut layer_menu_state = LayerMenuState {
index: 0,
input_shape: 0,
last_layer_index : model.layers.len().checked_sub(1),
layer_to_delete: None,
layer_to_add: None
};


let return_response = ui.horizontal_centered(|ui| create_layers_ui(ui, model, radius, layer_state, &mut layer_menu_state)).response;

match (layer_menu_state.layer_to_delete, layer_menu_state.last_layer_index) {
(Some(num), Some(last)) if num != last => {model.layers.remove(num); ()},
_ => ()
}

match layer_to_add {
match layer_menu_state.layer_to_add {
Some((num, x)) => model.layers.insert(num + 1, x),
_ => ()
}

return_response
}

pub fn model<'a>(model: &'a mut Model<f64>, layer_state: &'a mut LayerState) -> impl egui::Widget + 'a {
move |ui : &mut egui::Ui| model_ui(ui, model, layer_state)
fn create_layers_ui(ui: &mut egui::Ui, model: &mut Model<f64>, radius: f32, layer_state: &mut LayerState, layer_menu_state: &mut LayerMenuState) {
model.layers
.iter_mut()
.enumerate()
.for_each(|(num, model_layer)| {
let layer_neurons = model_layer.weights.len() as f32;
let layer_size = Vec2::new(radius * 2.5, radius * 4.0 * layer_neurons);
layer_menu_state.input_shape = layer_neurons as usize;
layer_menu_state.index = num;
ui.add_sized(layer_size, layer(model_layer, radius))
.context_menu(|ui| layer_context_menu(ui, layer_state, layer_menu_state));
});
}

fn layer_context_menu(ui : &mut egui::Ui, layer_state : &mut LayerState, layer_menu_state: &mut LayerMenuState) {
ui.label("Layer Actions:");
ui.menu_button("New", |ui| {
ui.text_edit_singleline(&mut layer_state.neuron_count_string).on_hover_text("Enter the number of neurons");
egui::ComboBox::from_label("Activation Function")
.selected_text(format!("{:?}", layer_state.activation))
.show_ui(ui, |ui| {
ui.selectable_value(&mut layer_state.activation, Activation::Sigmoid, "Sigmoid");
ui.selectable_value(&mut layer_state.activation, Activation::None, "None");
});

if ui.button("Add").clicked() {
let input_shape = layer_menu_state.input_shape;
let output_shape = layer_state.neuron_count_string.clone().parse().unwrap();
layer_menu_state.layer_to_add = Some((layer_menu_state.index, rust_mlp::Layer::from_size(input_shape, output_shape, layer_state.activation.clone())));
ui.close_menu();
};
});

// Safety : Can unwrap safely because layers > 1 in this code block
// so last_layer_num must exist.

if layer_menu_state.index != layer_menu_state.last_layer_index.unwrap() && ui.button("Delete").clicked() {
layer_menu_state.layer_to_delete = Some(layer_menu_state.index);
ui.close_menu();
}
}




18 changes: 2 additions & 16 deletions model-gui/src/widgets/neuron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,11 @@ use crate::{Pos2, Color32, Vec2, Sense, Stroke};
pub fn neuron_ui(ui: &mut egui::Ui, radius : f32, weights : &mut Vec<f64> ) -> egui::Response {
let (response, painter) = ui.allocate_painter(Vec2::new(2.0 * radius, 2.0 * radius), Sense::click());
let screen = response.rect;
//ui.label(format!("NEURON {:?}", screen));

//if ui.is_rect_visible(screen) {
if ui.is_rect_visible(screen) {
painter.circle_stroke(screen.center(), radius, Stroke::new(1.0, Color32::from_rgb(255, 255, 255)));
//}

if response.clicked() {
response.request_focus();
};

if response.has_focus() {
let mut weights_string = String::from("Weights: [");
for weight in weights {
write!(weights_string, "{:.3}, ", weight);
}
write!(weights_string, "]");
ui.label(egui::RichText::new(weights_string).size(12.0));
}

response
}

Expand Down

0 comments on commit 8f410bb

Please sign in to comment.