extract ui to module
This commit is contained in:
parent
4eccbf78e0
commit
6856a5830f
227
src/main.rs
227
src/main.rs
@ -1,224 +1,18 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
use bevy_ecs::prelude::*;
|
||||||
|
use eframe::egui;
|
||||||
use bevy_ecs::{
|
|
||||||
prelude::*,
|
|
||||||
query::WorldQuery,
|
|
||||||
system::{Commands, IntoSystem, Query, ReadOnlySystem, Res, ResMut, System},
|
|
||||||
};
|
|
||||||
use eframe::{
|
|
||||||
egui::{self, CursorIcon, Painter, Response, Sense},
|
|
||||||
emath::{Pos2, Rect, RectTransform, Vec2},
|
|
||||||
epaint::{Color32, Hsva, Stroke},
|
|
||||||
};
|
|
||||||
use geometry::{Line, LineBundle, Point, PointId, PointPos, PointPosQueryMut};
|
|
||||||
|
|
||||||
mod geometry;
|
mod geometry;
|
||||||
pub mod optimization;
|
pub mod optimization;
|
||||||
mod relations;
|
mod relations;
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
mod tracing;
|
mod tracing;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
fn init(mut commands: Commands) {
|
fn init(mut commands: Commands) {
|
||||||
let p1 = geometry::insert_point_at(&mut commands, (10., 30.));
|
let p1 = geometry::insert_point_at(&mut commands, (10., 30.));
|
||||||
let p2 = geometry::insert_point_at(&mut commands, (-20., 15.));
|
let p2 = geometry::insert_point_at(&mut commands, (-20., 15.));
|
||||||
geometry::insert_point_at(&mut commands, (0., -10.));
|
geometry::insert_point_at(&mut commands, (0., -10.));
|
||||||
commands.spawn(LineBundle::new(p1, p2));
|
commands.spawn(geometry::LineBundle::new(p1, p2));
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
struct ContextRes(egui::Context);
|
|
||||||
|
|
||||||
impl Deref for ContextRes {
|
|
||||||
type Target = egui::Context;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(ctx: Res<ContextRes>) {
|
|
||||||
ctx.request_repaint();
|
|
||||||
ctx.set_visuals(egui::Visuals::dark());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toolbar(
|
|
||||||
ctx: Res<ContextRes>,
|
|
||||||
selected: Query<Entity, With<Selected>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
mut tool: ResMut<Tool>,
|
|
||||||
) {
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| {
|
|
||||||
// ui.heading("sketchrs");
|
|
||||||
let mut selected_tool = *tool;
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.radio_value(&mut selected_tool, Tool::Select, "Select");
|
|
||||||
ui.radio_value(&mut selected_tool, Tool::Move, "Move");
|
|
||||||
ui.radio_value(&mut selected_tool, Tool::AddPoint, "Add Point");
|
|
||||||
ui.radio_value(&mut selected_tool, Tool::AddLine, "Add Line");
|
|
||||||
});
|
|
||||||
if selected_tool != *tool {
|
|
||||||
*tool = selected_tool;
|
|
||||||
|
|
||||||
for selected in selected.iter() {
|
|
||||||
commands.entity(selected).remove::<Selected>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShowEntitiesSchedule(Schedule);
|
|
||||||
|
|
||||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[system_set(base)]
|
|
||||||
pub enum ShowEntitiesStage {
|
|
||||||
Update,
|
|
||||||
Input,
|
|
||||||
Tools,
|
|
||||||
PostTools,
|
|
||||||
Paint,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ShowEntitiesSchedule {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut schedule = Schedule::new();
|
|
||||||
schedule.configure_sets(
|
|
||||||
(
|
|
||||||
ShowEntitiesStage::Update,
|
|
||||||
ShowEntitiesStage::Input,
|
|
||||||
ShowEntitiesStage::Tools,
|
|
||||||
ShowEntitiesStage::PostTools,
|
|
||||||
ShowEntitiesStage::Paint,
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
);
|
|
||||||
schedule.add_systems(
|
|
||||||
(geometry::update_point_pos, geometry::update_line_pos)
|
|
||||||
.in_base_set(ShowEntitiesStage::Update),
|
|
||||||
);
|
|
||||||
schedule.add_systems(
|
|
||||||
(update_hover_point, update_hover_line).in_base_set(ShowEntitiesStage::Input),
|
|
||||||
);
|
|
||||||
schedule.add_systems(
|
|
||||||
(
|
|
||||||
select_tool.run_if(is_tool_active(Tool::Select)),
|
|
||||||
move_tool.run_if(is_tool_active(Tool::Move)),
|
|
||||||
add_point_tool.run_if(is_tool_active(Tool::AddPoint)),
|
|
||||||
add_line_tool.run_if(is_tool_active(Tool::AddLine)),
|
|
||||||
add_relation_tool.run_if(is_tool_active(Tool::AddRelation)),
|
|
||||||
)
|
|
||||||
.distributive_run_if(is_hovered)
|
|
||||||
.in_base_set(ShowEntitiesStage::Tools),
|
|
||||||
);
|
|
||||||
schedule
|
|
||||||
.add_system(geometry::remove_dangling_lines.in_base_set(ShowEntitiesStage::PostTools));
|
|
||||||
schedule.add_systems(
|
|
||||||
(paint_lines, paint_points)
|
|
||||||
.chain()
|
|
||||||
.in_base_set(ShowEntitiesStage::Paint),
|
|
||||||
);
|
|
||||||
Self(schedule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn central_panel(world: &mut World, mut schedule: Local<ShowEntitiesSchedule>) {
|
|
||||||
let ctx = world.get_resource::<ContextRes>().unwrap().0.clone();
|
|
||||||
egui::CentralPanel::default()
|
|
||||||
.frame(egui::Frame::none())
|
|
||||||
.show(&ctx, |ui| {
|
|
||||||
let sense = match *world.resource::<Tool>() {
|
|
||||||
Tool::Move => Sense::drag(),
|
|
||||||
Tool::Select | Tool::AddPoint | Tool::AddLine | Tool::AddRelation => Sense::click(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (response, painter) = ui.allocate_painter(ui.available_size(), sense);
|
|
||||||
let to_screen = RectTransform::from_to(
|
|
||||||
Rect::from_center_size(Pos2::ZERO, response.rect.size() / 2.0),
|
|
||||||
response.rect,
|
|
||||||
);
|
|
||||||
world.insert_resource(ToScreen(to_screen));
|
|
||||||
world.insert_resource(ResponseRes(response));
|
|
||||||
world.insert_resource(PainterRes(painter));
|
|
||||||
|
|
||||||
schedule.0.run(world);
|
|
||||||
|
|
||||||
world.remove_resource::<PainterRes>();
|
|
||||||
world.remove_resource::<ResponseRes>();
|
|
||||||
world.remove_resource::<ToScreen>();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(WorldQuery)]
|
|
||||||
struct SelectableEntity<'a> {
|
|
||||||
id: Entity,
|
|
||||||
point: Option<&'a Point>,
|
|
||||||
line: Option<&'a Line>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn side_panel_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
selected: Query<SelectableEntity, With<Selected>>,
|
|
||||||
tool: Res<Tool>,
|
|
||||||
) {
|
|
||||||
let tool = *tool;
|
|
||||||
ui.vertical(|ui| match tool {
|
|
||||||
Tool::Select => {
|
|
||||||
let mut count = 0;
|
|
||||||
selected.for_each(|sel| {
|
|
||||||
count += 1;
|
|
||||||
if sel.point.is_some() {
|
|
||||||
ui.label(format!("Selected point {}", sel.id.index()));
|
|
||||||
} else if sel.line.is_some() {
|
|
||||||
ui.label(format!("Selected line {}", sel.id.index()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if count == 0 {
|
|
||||||
ui.label("Nothing selected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Tool::Move => {
|
|
||||||
let mut count = 0;
|
|
||||||
selected.for_each(|sel| {
|
|
||||||
count += 1;
|
|
||||||
if sel.point.is_some() {
|
|
||||||
ui.label(format!("Selected point {}", sel.id.index()));
|
|
||||||
} else if sel.line.is_some() {
|
|
||||||
ui.label(format!("Selected line {}", sel.id.index()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if count == 0 {
|
|
||||||
ui.label("Nothing selected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Tool::AddPoint => {
|
|
||||||
ui.label("Click to add a point");
|
|
||||||
}
|
|
||||||
Tool::AddLine => {
|
|
||||||
ui.label("Click to add a line");
|
|
||||||
}
|
|
||||||
Tool::AddRelation => {
|
|
||||||
ui.label("Click to add a relation");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn side_panel(
|
|
||||||
ctx: Res<ContextRes>,
|
|
||||||
selected: Query<SelectableEntity, With<Selected>>,
|
|
||||||
tool: Res<Tool>,
|
|
||||||
) {
|
|
||||||
egui::SidePanel::right("side_panel")
|
|
||||||
.resizable(true)
|
|
||||||
.default_width(150.0)
|
|
||||||
.width_range(80.0..=200.0)
|
|
||||||
.show(&ctx, |ui| side_panel_ui(ui, selected, tool));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bottom_panel(ctx: Res<ContextRes>) {
|
|
||||||
egui::TopBottomPanel::bottom("bottom_panel").show(&ctx, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Status:");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
@ -231,6 +25,7 @@ impl Default for MyApp {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyApp {
|
impl MyApp {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -238,27 +33,29 @@ impl MyApp {
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
tracing::setup(&mut world);
|
tracing::setup(&mut world);
|
||||||
|
|
||||||
|
world.init_resource::<ui::Tool>();
|
||||||
|
|
||||||
let mut init_sched = Schedule::new();
|
let mut init_sched = Schedule::new();
|
||||||
init_sched.add_system(init);
|
init_sched.add_system(init);
|
||||||
init_sched.run(&mut world);
|
init_sched.run(&mut world);
|
||||||
|
|
||||||
let mut update = Schedule::default();
|
let mut update_schedule = Schedule::default();
|
||||||
update.add_systems((prepare, toolbar, side_panel, bottom_panel, central_panel).chain());
|
ui::add_to_schedule(&mut update_schedule);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
world,
|
world,
|
||||||
update_schedule: update,
|
update_schedule,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
self.world.insert_resource(ContextRes(ctx.clone()));
|
self.world.insert_resource(ui::ContextRes(ctx.clone()));
|
||||||
|
|
||||||
self.update_schedule.run(&mut self.world);
|
self.update_schedule.run(&mut self.world);
|
||||||
|
|
||||||
self.world.remove_resource::<ContextRes>();
|
self.world.remove_resource::<ui::ContextRes>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
570
src/ui.rs
Normal file
570
src/ui.rs
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use bevy_ecs::{prelude::*, query::WorldQuery, system::ReadOnlySystem};
|
||||||
|
use eframe::{
|
||||||
|
egui::{self, CursorIcon, Painter, Response, Sense},
|
||||||
|
emath::RectTransform,
|
||||||
|
epaint::{Color32, Hsva, Pos2, Rect, Stroke, Vec2},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::geometry::{self};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Resource)]
|
||||||
|
pub enum Tool {
|
||||||
|
Select,
|
||||||
|
Move,
|
||||||
|
AddPoint,
|
||||||
|
AddLine,
|
||||||
|
AddRelation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::derivable_impls)]
|
||||||
|
impl Default for Tool {
|
||||||
|
fn default() -> Self {
|
||||||
|
Tool::Select
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct UiRes {
|
||||||
|
response: Response,
|
||||||
|
painter: Painter,
|
||||||
|
to_screen: ToScreen,
|
||||||
|
}
|
||||||
|
struct ToScreen(RectTransform);
|
||||||
|
|
||||||
|
impl ToScreen {
|
||||||
|
fn transform_pos(&self, pos: &geometry::PointPos) -> Pos2 {
|
||||||
|
self.0 * Pos2::new(pos.x as f32, pos.y as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inverse_transform(&self, pos: Pos2) -> Pos2 {
|
||||||
|
self.0.inverse() * pos
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inverse_transform_to_point(&self, pos: Pos2) -> geometry::PointPos {
|
||||||
|
let pos = self.inverse_transform(pos);
|
||||||
|
geometry::PointPos::new(pos.x as f64, pos.y as f64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Hovered;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Selected;
|
||||||
|
|
||||||
|
fn color_for_var_status(status: geometry::VarStatus) -> Hsva {
|
||||||
|
use geometry::VarStatus::*;
|
||||||
|
match status {
|
||||||
|
Free => Hsva::new(200. / 360., 0.90, 0.80, 1.0),
|
||||||
|
Dependent => todo!(),
|
||||||
|
Unique => todo!(),
|
||||||
|
Overconstrained => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const POINT_RADIUS: f32 = 3.0;
|
||||||
|
|
||||||
|
fn update_hover_point(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
points: Query<(geometry::PointId, &geometry::ComputedPointPos)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
points.for_each(|(id, pos)| {
|
||||||
|
let hovered = if let Some(hover_pos) = ui.response.hover_pos() {
|
||||||
|
let center = ui.to_screen.transform_pos(pos);
|
||||||
|
|
||||||
|
(hover_pos - center).length() < (POINT_RADIUS * 3.)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if hovered {
|
||||||
|
commands.entity(id).insert(Hovered);
|
||||||
|
} else {
|
||||||
|
commands.entity(id).remove::<Hovered>();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_hover_line(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
lines: Query<(Entity, &geometry::ComputedLinePos)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
lines.for_each(|(id, pos)| {
|
||||||
|
let hovered = if let Some(hover_pos) = ui.response.hover_pos() {
|
||||||
|
let points = [
|
||||||
|
ui.to_screen.transform_pos(&pos.start),
|
||||||
|
ui.to_screen.transform_pos(&pos.end),
|
||||||
|
];
|
||||||
|
|
||||||
|
let b = points[1] - points[0];
|
||||||
|
let a = hover_pos - points[0];
|
||||||
|
let p = a.dot(b) / b.dot(b);
|
||||||
|
let perp = a - (p * b);
|
||||||
|
((0.)..=1.).contains(&p) && perp.length() < 5.0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if hovered {
|
||||||
|
commands.entity(id).insert(Hovered);
|
||||||
|
} else {
|
||||||
|
commands.entity(id).remove::<Hovered>();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_tool(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
hovered: Query<Entity, With<Hovered>>,
|
||||||
|
selected: Query<Entity, With<Selected>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
ui.response.ctx.output_mut(|output| {
|
||||||
|
output.cursor_icon = if !hovered.is_empty() {
|
||||||
|
CursorIcon::PointingHand
|
||||||
|
} else {
|
||||||
|
CursorIcon::Default
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ui.response.clicked() {
|
||||||
|
if !ui.response.ctx.input(|input| input.modifiers.shift) {
|
||||||
|
selected.for_each(|selected| {
|
||||||
|
commands.entity(selected).remove::<Selected>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// TODO: choose which to select
|
||||||
|
if let Some(hovered) = hovered.iter().next() {
|
||||||
|
commands.entity(hovered).insert(Selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.response
|
||||||
|
.ctx
|
||||||
|
.input(|input| input.key_pressed(egui::Key::Escape))
|
||||||
|
{
|
||||||
|
selected.for_each(|selected| {
|
||||||
|
commands.entity(selected).remove::<Selected>();
|
||||||
|
});
|
||||||
|
} else if ui
|
||||||
|
.response
|
||||||
|
.ctx
|
||||||
|
.input(|input| input.key_pressed(egui::Key::Delete))
|
||||||
|
{
|
||||||
|
selected.for_each(|selected| {
|
||||||
|
commands.entity(selected).despawn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct DragDelta(Vec2);
|
||||||
|
|
||||||
|
// TODO: move other entities
|
||||||
|
fn move_tool(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
mut drag_delta: Local<DragDelta>,
|
||||||
|
mut point_pos: geometry::PointPosQueryMut,
|
||||||
|
hovered: Query<(Entity, &geometry::Point), With<Hovered>>,
|
||||||
|
selected: Query<(Entity, &geometry::Point), With<Selected>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let response = &ui.response;
|
||||||
|
let to_screen = &ui.to_screen;
|
||||||
|
let hover_pos = response.hover_pos().unwrap();
|
||||||
|
|
||||||
|
response.ctx.output_mut(|o| {
|
||||||
|
o.cursor_icon = if !selected.is_empty() {
|
||||||
|
CursorIcon::Grabbing
|
||||||
|
} else if !hovered.is_empty() {
|
||||||
|
CursorIcon::Grab
|
||||||
|
} else {
|
||||||
|
CursorIcon::Move
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let selected = if response.drag_started() {
|
||||||
|
// TODO: choose which to select
|
||||||
|
let to_select = hovered.iter().next();
|
||||||
|
if let Some(hovered) = to_select {
|
||||||
|
commands.entity(hovered.0).insert(Selected);
|
||||||
|
}
|
||||||
|
to_select
|
||||||
|
} else if response.drag_released() {
|
||||||
|
selected.for_each(|selected| {
|
||||||
|
commands.entity(selected.0).remove::<Selected>();
|
||||||
|
});
|
||||||
|
drag_delta.0 = Vec2::ZERO;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
selected.iter().next()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((_, point)) = selected {
|
||||||
|
if response.drag_started() {
|
||||||
|
let drag_point_pos = point_pos.get(point).unwrap();
|
||||||
|
drag_delta.0 = hover_pos - to_screen.transform_pos(&drag_point_pos);
|
||||||
|
}
|
||||||
|
let move_to = to_screen.inverse_transform_to_point(hover_pos - drag_delta.0);
|
||||||
|
point_pos.set(point, move_to).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_point(commands: &mut Commands, pos: Pos2, to_screen: &ToScreen) -> geometry::PointId {
|
||||||
|
let point_pos = to_screen.inverse_transform(pos);
|
||||||
|
let point_pos = (point_pos.x as f64, point_pos.y as f64);
|
||||||
|
geometry::insert_point_at(commands, point_pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_point_tool(ui: Res<UiRes>, mut commands: Commands) {
|
||||||
|
let hover_pos = ui.response.hover_pos().unwrap();
|
||||||
|
if ui.response.clicked() {
|
||||||
|
add_point(&mut commands, hover_pos, &ui.to_screen);
|
||||||
|
} else {
|
||||||
|
ui.painter
|
||||||
|
.circle_filled(hover_pos, POINT_RADIUS, Color32::WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_line_tool(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
hovered: Query<Entity, (With<Hovered>, With<geometry::Point>)>,
|
||||||
|
selected: Query<(Entity, &geometry::ComputedPointPos), With<Selected>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let UiRes {
|
||||||
|
response,
|
||||||
|
painter,
|
||||||
|
to_screen,
|
||||||
|
} = &*ui;
|
||||||
|
let hover_pos = response.hover_pos().unwrap();
|
||||||
|
|
||||||
|
match (selected.iter().next(), response.clicked()) {
|
||||||
|
(None, false) => {
|
||||||
|
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
|
||||||
|
}
|
||||||
|
(None, true) => {
|
||||||
|
let point_id = match hovered.iter().next() {
|
||||||
|
Some(hovered) => hovered,
|
||||||
|
None => add_point(&mut commands, hover_pos, &to_screen),
|
||||||
|
};
|
||||||
|
commands.entity(point_id).insert(Selected);
|
||||||
|
}
|
||||||
|
(Some((_, start_point_pos)), false) => {
|
||||||
|
let points = [to_screen.transform_pos(start_point_pos), hover_pos];
|
||||||
|
|
||||||
|
let stroke = Stroke::new(2.0, Color32::DARK_GRAY);
|
||||||
|
|
||||||
|
painter.line_segment(points, stroke);
|
||||||
|
|
||||||
|
painter.circle_filled(hover_pos, POINT_RADIUS, Color32::DARK_GRAY);
|
||||||
|
}
|
||||||
|
(Some((start_point_id, _)), true) => {
|
||||||
|
// TODO: add point if no hover point
|
||||||
|
let end_point = hovered
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| add_point(&mut commands, hover_pos, &to_screen));
|
||||||
|
|
||||||
|
let line = geometry::LineBundle::new(start_point_id, end_point);
|
||||||
|
commands.spawn(line);
|
||||||
|
|
||||||
|
selected.for_each(|(selected_id, _)| {
|
||||||
|
commands.entity(selected_id).remove::<Selected>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_relation_tool(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
hovered: Query<Entity, With<Hovered>>,
|
||||||
|
selected: Query<Entity, With<Selected>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let response = &ui.response;
|
||||||
|
response.ctx.output_mut(|o| {
|
||||||
|
o.cursor_icon = if !hovered.is_empty() {
|
||||||
|
CursorIcon::PointingHand
|
||||||
|
} else {
|
||||||
|
CursorIcon::Default
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if response.clicked() {
|
||||||
|
// TODO: choose which to select
|
||||||
|
if let Some(hovered) = hovered.iter().next() {
|
||||||
|
commands.entity(hovered).insert(Selected);
|
||||||
|
} else {
|
||||||
|
selected.for_each(|selected| {
|
||||||
|
commands.entity(selected).remove::<Selected>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_hovered(ui: Res<UiRes>) -> bool {
|
||||||
|
ui.response.hover_pos().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_tool_active(tool: Tool) -> impl System<In = (), Out = bool> + ReadOnlySystem {
|
||||||
|
IntoSystem::into_system(move |active_tool: Res<Tool>| *active_tool == tool)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_lines(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
lines: Query<(Entity, &geometry::ComputedLinePos)>,
|
||||||
|
hovered: Query<(), With<Hovered>>,
|
||||||
|
selected: Query<(), With<Selected>>,
|
||||||
|
) {
|
||||||
|
lines.for_each(|(id, pos)| {
|
||||||
|
let points = [
|
||||||
|
ui.to_screen.transform_pos(&pos.start),
|
||||||
|
ui.to_screen.transform_pos(&pos.end),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut color = color_for_var_status(pos.status);
|
||||||
|
color.v -= 0.6;
|
||||||
|
if hovered.contains(id) || selected.contains(id) {
|
||||||
|
color.s -= 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke = Stroke::new(2.0, color);
|
||||||
|
|
||||||
|
ui.painter.line_segment(points, stroke);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_points(
|
||||||
|
ui: Res<UiRes>,
|
||||||
|
points: Query<(Entity, &geometry::ComputedPointPos)>,
|
||||||
|
hovered: Query<(), With<Hovered>>,
|
||||||
|
selected: Query<(), With<Selected>>,
|
||||||
|
) {
|
||||||
|
points.for_each(|(id, pos)| {
|
||||||
|
let center = ui.to_screen.transform_pos(pos);
|
||||||
|
|
||||||
|
let color = color_for_var_status(pos.status);
|
||||||
|
let stroke = if selected.contains(id) || hovered.contains(id) {
|
||||||
|
// color.s -= 0.8;
|
||||||
|
Stroke::new(1.0, Color32::WHITE)
|
||||||
|
} else {
|
||||||
|
Stroke::default()
|
||||||
|
};
|
||||||
|
ui.painter.circle(center, POINT_RADIUS, color, stroke);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct ContextRes(pub egui::Context);
|
||||||
|
|
||||||
|
impl Deref for ContextRes {
|
||||||
|
type Target = egui::Context;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(ctx: Res<ContextRes>) {
|
||||||
|
// ctx.request_repaint();
|
||||||
|
ctx.set_visuals(egui::Visuals::dark());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toolbar(
|
||||||
|
ctx: Res<ContextRes>,
|
||||||
|
selected: Query<Entity, With<Selected>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut tool: ResMut<Tool>,
|
||||||
|
) {
|
||||||
|
egui::TopBottomPanel::top("top_panel").show(&ctx, |ui| {
|
||||||
|
// ui.heading("sketchrs");
|
||||||
|
let mut selected_tool = *tool;
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.radio_value(&mut selected_tool, Tool::Select, "Select");
|
||||||
|
ui.radio_value(&mut selected_tool, Tool::Move, "Move");
|
||||||
|
ui.radio_value(&mut selected_tool, Tool::AddPoint, "Add Point");
|
||||||
|
ui.radio_value(&mut selected_tool, Tool::AddLine, "Add Line");
|
||||||
|
});
|
||||||
|
if selected_tool != *tool {
|
||||||
|
*tool = selected_tool;
|
||||||
|
|
||||||
|
for selected in selected.iter() {
|
||||||
|
commands.entity(selected).remove::<Selected>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShowEntitiesSchedule(Schedule);
|
||||||
|
|
||||||
|
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[system_set(base)]
|
||||||
|
pub enum ShowEntitiesStage {
|
||||||
|
Update,
|
||||||
|
UpdateFlush,
|
||||||
|
Input,
|
||||||
|
InputFlush,
|
||||||
|
Tools,
|
||||||
|
PostTools,
|
||||||
|
ToolsFlush,
|
||||||
|
Paint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ShowEntitiesSchedule {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut schedule = Schedule::new();
|
||||||
|
schedule
|
||||||
|
.configure_sets(
|
||||||
|
(
|
||||||
|
ShowEntitiesStage::Update,
|
||||||
|
ShowEntitiesStage::UpdateFlush,
|
||||||
|
ShowEntitiesStage::Input,
|
||||||
|
ShowEntitiesStage::InputFlush,
|
||||||
|
ShowEntitiesStage::Tools,
|
||||||
|
ShowEntitiesStage::ToolsFlush,
|
||||||
|
ShowEntitiesStage::PostTools,
|
||||||
|
ShowEntitiesStage::Paint,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
(geometry::update_point_pos, geometry::update_line_pos)
|
||||||
|
.in_base_set(ShowEntitiesStage::Update),
|
||||||
|
)
|
||||||
|
.add_system(apply_system_buffers.in_base_set(ShowEntitiesStage::UpdateFlush))
|
||||||
|
.add_systems(
|
||||||
|
(update_hover_point, update_hover_line).in_base_set(ShowEntitiesStage::Input),
|
||||||
|
)
|
||||||
|
.add_system(apply_system_buffers.in_base_set(ShowEntitiesStage::InputFlush))
|
||||||
|
.add_systems(
|
||||||
|
(
|
||||||
|
select_tool.run_if(is_tool_active(Tool::Select)),
|
||||||
|
move_tool.run_if(is_tool_active(Tool::Move)),
|
||||||
|
add_point_tool.run_if(is_tool_active(Tool::AddPoint)),
|
||||||
|
add_line_tool.run_if(is_tool_active(Tool::AddLine)),
|
||||||
|
add_relation_tool.run_if(is_tool_active(Tool::AddRelation)),
|
||||||
|
)
|
||||||
|
.distributive_run_if(is_hovered)
|
||||||
|
.in_base_set(ShowEntitiesStage::Tools),
|
||||||
|
)
|
||||||
|
.add_system(geometry::remove_dangling_lines.in_base_set(ShowEntitiesStage::PostTools))
|
||||||
|
.add_system(apply_system_buffers.in_base_set(ShowEntitiesStage::ToolsFlush))
|
||||||
|
.add_systems(
|
||||||
|
(paint_lines, paint_points)
|
||||||
|
.chain()
|
||||||
|
.in_base_set(ShowEntitiesStage::Paint),
|
||||||
|
);
|
||||||
|
Self(schedule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn central_panel(world: &mut World, mut schedule: Local<ShowEntitiesSchedule>) {
|
||||||
|
let ctx = world.get_resource::<ContextRes>().unwrap().0.clone();
|
||||||
|
egui::CentralPanel::default()
|
||||||
|
.frame(egui::Frame::none())
|
||||||
|
.show(&ctx, |ui| {
|
||||||
|
let sense = match *world.resource::<Tool>() {
|
||||||
|
Tool::Move => Sense::drag(),
|
||||||
|
Tool::Select | Tool::AddPoint | Tool::AddLine | Tool::AddRelation => Sense::click(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (response, painter) = ui.allocate_painter(ui.available_size(), sense);
|
||||||
|
let to_screen = ToScreen(RectTransform::from_to(
|
||||||
|
Rect::from_center_size(Pos2::ZERO, response.rect.size() / 2.0),
|
||||||
|
response.rect,
|
||||||
|
));
|
||||||
|
world.insert_resource(UiRes {
|
||||||
|
response,
|
||||||
|
painter,
|
||||||
|
to_screen,
|
||||||
|
});
|
||||||
|
|
||||||
|
schedule.0.run(world);
|
||||||
|
|
||||||
|
world.remove_resource::<UiRes>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(WorldQuery)]
|
||||||
|
struct SelectableEntity<'a> {
|
||||||
|
id: Entity,
|
||||||
|
point: Option<&'a geometry::Point>,
|
||||||
|
line: Option<&'a geometry::Line>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn side_panel_ui(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
selected: Query<SelectableEntity, With<Selected>>,
|
||||||
|
tool: Res<Tool>,
|
||||||
|
) {
|
||||||
|
let tool = *tool;
|
||||||
|
ui.vertical(|ui| match tool {
|
||||||
|
Tool::Select => {
|
||||||
|
let mut count = 0;
|
||||||
|
selected.for_each(|sel| {
|
||||||
|
count += 1;
|
||||||
|
if sel.point.is_some() {
|
||||||
|
ui.label(format!("Selected point {}", sel.id.index()));
|
||||||
|
} else if sel.line.is_some() {
|
||||||
|
ui.label(format!("Selected line {}", sel.id.index()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if count == 0 {
|
||||||
|
ui.label("Nothing selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tool::Move => {
|
||||||
|
let mut count = 0;
|
||||||
|
selected.for_each(|sel| {
|
||||||
|
count += 1;
|
||||||
|
if sel.point.is_some() {
|
||||||
|
ui.label(format!("Selected point {}", sel.id.index()));
|
||||||
|
} else if sel.line.is_some() {
|
||||||
|
ui.label(format!("Selected line {}", sel.id.index()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if count == 0 {
|
||||||
|
ui.label("Nothing selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tool::AddPoint => {
|
||||||
|
ui.label("Click to add a point");
|
||||||
|
}
|
||||||
|
Tool::AddLine => {
|
||||||
|
ui.label("Click to add a line");
|
||||||
|
}
|
||||||
|
Tool::AddRelation => {
|
||||||
|
ui.label("Click to add a relation");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn side_panel(
|
||||||
|
ctx: Res<ContextRes>,
|
||||||
|
selected: Query<SelectableEntity, With<Selected>>,
|
||||||
|
tool: Res<Tool>,
|
||||||
|
) {
|
||||||
|
egui::SidePanel::right("side_panel")
|
||||||
|
.resizable(true)
|
||||||
|
.default_width(150.0)
|
||||||
|
.width_range(80.0..=200.0)
|
||||||
|
.show(&ctx, |ui| side_panel_ui(ui, selected, tool));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bottom_panel(ctx: Res<ContextRes>) {
|
||||||
|
egui::TopBottomPanel::bottom("bottom_panel").show(&ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Status:");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_schedule(schedule: &mut Schedule) {
|
||||||
|
schedule.add_systems((prepare, toolbar, side_panel, bottom_panel, central_panel).chain());
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user