refactor: separate renderer code

This commit is contained in:
Matthieu Bessat 2024-07-07 13:15:38 +02:00
parent fe61e12903
commit 2670e7d1da
7 changed files with 273 additions and 234 deletions

11
TODO.md
View file

@ -16,13 +16,16 @@
- [x] 2024-06-28: oauth2 with oauth2c
- [x] 2024-06-29: generate changeset
- [x] 2024-07-01: upload changeset
- [ ] 2024-07-01: add success or error messages buffer
- [ ] 2024-07-01: refacto with clippy
- [ ] 2024-07-01: configure proxy via env variable
- [x] 2024-07-01: add success or error messages buffer
- [x] 2024-07-01: refacto with clippy
- [ ] refacto renderer
- [ ] vim-like commands sequence
- [ ] show every singular nodes
- [ ] create node
- [ ] configure proxy via env variable
- [ ] add basic CLI interface
- oauth2 status command
- [ ] show icons on POI
- [ ] vim-like commands sequence
- [ ] vim-like text manual command bottom.
- `:Goto <coords>`
- `:Info`

View file

@ -13,25 +13,24 @@ use std::{collections::{HashMap, HashSet}, fs, process, sync::{Arc, Mutex}, thre
use anyhow::{Context, Result, anyhow};
use data::{bbox_center, session::{read_session, write_session, Session}, IndexedData};
use data::{session::{read_session, write_session, Session}, IndexedData};
use fully_pub::fully_pub;
use layers::{load_osm_data, load_osm_zone, load_static_cities_layer, Layer, LayerKind};
use osm_api::OSMApiClient;
use raylib::prelude::*;
use ui::{edit_tags, inputs::{collect_actions_from_inputs, UiAction}, messages::{render_messages, UiMessage, UiMessageLevel}, selection::get_objects_in_area};
use utils::{deg2num, get_envelope_from_bounds};
use ui::{edit_tags, inputs::{collect_actions_from_inputs, UiAction}, messages::{UiMessage, UiMessageLevel}, renderer::render_app, selection::get_objects_in_area};
use utils::deg2num;
use xdg::BaseDirectories;
use std::io::Read;
use serde::{Deserialize};
use serde::Deserialize;
use ureq::Agent;
use rand::Rng;
use crate::utils::CanvasPos;
use crate::utils::Invertion;
use crate::utils::ToSlice;
pub const USER_AGENT: &str = "BobOSM dev";
#[derive(Debug)]
#[derive(Debug, Clone)]
struct Camera {
bounds: (Vector2, Vector2),
center: Vector2,
@ -41,6 +40,14 @@ struct Camera {
canvas_to_real_factor: f32
}
#[fully_pub]
struct AppContext {
/// snapshot of camera state
camera: Camera,
state: Arc<Mutex<AppState>>,
layers: Arc<Mutex<Vec<Layer>>>
}
#[derive(PartialEq, Debug)]
#[fully_pub]
enum UiMode {
@ -270,7 +277,6 @@ fn main() -> Result<()> {
UiAction::NavigatePan(move_v) => {
camera.bounds.0 += move_v;
camera.bounds.1 += move_v;
},
UiAction::NavigateZoom { focus, factor } => {
camera.bounds.0 = focus + (camera.bounds.0 - focus).scale_by(factor);
@ -347,177 +353,11 @@ fn main() -> Result<()> {
camera.zoom = 1.0/(camera.bounds.0.x - camera.bounds.1.x).abs();
camera.map_zoom = 4.680_851*camera.zoom;
let mut d = rl.begin_drawing(&thread);
// d.draw_texture(&static_t, 0, 0, Color::WHITE);
d.clear_background(Color::BLACK);
// if let Some(texture) = tmp_texture {
// println!("{:?}", &texture);
// println!("center_canvas_pos {:?}", center_canvas_pos);
// d.draw_texture(texture, center_canvas_pos.x.ceil() as i32, center_canvas_pos.y.ceil() as i32, Color::WHITE);
// }
// Repère A: Canvas
// Repère B: Virtual canvas
// draw raster tiles
// render layers
let current_bounds_aabb = get_envelope_from_bounds(camera.bounds, 0.30);
{ // layers
let app_state_ref = app_state.lock().unwrap();
for (layer_i, layer) in layers.lock().unwrap().iter().enumerate() {
// Draw ways
if let Some(tree) = &layer.ways_tree {
for way in tree.locate_in_envelope_intersecting(&current_bounds_aabb) {
let mut last_p: Option<Vector2> = None;
for p in &way.positions {
if let Some(last_p_v) = last_p {
d.draw_line_ex(
last_p_v.to_canvas_pos(&camera),
p.to_canvas_pos(&camera),
1.0,
Color::BLUE
)
}
last_p = Some(*p);
}
let bbox_center_canvas = bbox_center(way.bbox).to_canvas_pos(&camera);
// draw name in center of polygon
if !way.name.is_empty() && (
(camera.zoom <= 0.9 && way.importance >= 10) ||
(camera.zoom >= 1.0 && way.importance >= 9) ||
camera.zoom >= 1.2
)
{
d.draw_text(
&way.name.to_string(),
bbox_center_canvas.x.ceil() as i32 - ((way.name.len()/2)*6) as i32,
bbox_center_canvas.y.ceil() as i32 - 5,
12,
Color::WHITE
);
}
}
}
// Draw POI points of interest
if let Some(tree) = &layer.pois_tree {
for point in tree.locate_in_envelope(&current_bounds_aabb) {
let canvas_pos = point.pos.to_canvas_pos(&camera);
let size = 2.0*(((1.)/(1.+ (-(0.4 * camera.zoom - 4.)).exp() )) + 0.2);
// check if object is selected.
let is_selected: bool =
if let Some(selected_elements) = app_state_ref.selected_elements_per_layer.get(layer_i) {
selected_elements.iter().any(|e_id| *e_id == point.id)
} else {
false
};
let line_thickness: f32 =
(match point.importance {
10 => 5.,
9 => 3.,
_ => 3.0
}) +
(if is_selected { 4.0 } else { 0. });
let color = if is_selected {
Color::YELLOW
} else {
match point.importance {
10 => Color::GREEN,
9 => Color::RED,
3 => Color::PURPLE,
_ => Color::new(255, 255, 255, 100)
}
};
d.draw_poly_lines_ex(
canvas_pos,
5,
match point.importance {
10 => size*19.,
9 => size*8.,
_ => size*3.0
},
0.,
line_thickness,
color
);
if
(camera.zoom <= 0.9 && point.importance >= 10) ||
(camera.zoom >= 1.0 && point.importance >= 9) ||
camera.zoom >= 1.2
{
d.draw_text(
&point.name.to_string(),
canvas_pos.x.ceil() as i32 + 8,
canvas_pos.y.ceil() as i32,
match point.importance {
10 => 17,
9 => 14,
_ => 12
},
Color::WHITE
);
}
}
}
}
}
d.draw_circle_sector_lines(mouse_pos, 10.0, 0.0, 360.0, 1, Color::MAGENTA);
{
// draw meta/debug info
let app_state_ref = app_state.lock().unwrap();
d.draw_text(&format!("Zoom: {:?}, Bounds: {:?}", camera.zoom, camera.bounds), 13, 12, 15, Color::WHITE);
d.draw_text(&format!("Mouse: {:?}", real_mouse_pos), 13, 30, 15, Color::WHITE);
d.draw_text(&format!("Center: {:?}", camera.center), 13, 45, 15, Color::WHITE);
// draw app mode
d.draw_text(
&format!("M: {}",
match app_state_ref.ui_mode {
UiMode::View => "View",
UiMode::Edit => "Edit"
})
,
12, screen_def.1 - 50,
15,
Color::WHITE
);
// draw loading indicator
if app_state_ref.loading {
// TODO: add a sprite
d.draw_circle_v(
Vector2::new(20., (screen_def.1-20) as f32),
10.0,
Color::WHITE
)
}
// draw UI messages and alerts
render_messages(
&app_state_ref.messages,
&mut d,
&screen_def
)
}
// draw target cross (center fixed cursor)
let cross_width = 6;
d.draw_line_v(
Vector2::new(((screen_def.0/2) - cross_width) as f32, (screen_def.1/2) as f32),
Vector2::new(((screen_def.0/2) + cross_width) as f32, (screen_def.1/2) as f32),
Color::WHITE
);
d.draw_line_v(
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) - cross_width) as f32),
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) + cross_width) as f32),
Color::WHITE
);
render_app(&mut rl, &thread, AppContext {
state: app_state.clone(),
layers: layers.clone(),
camera: camera.clone()
})
}
Ok(())
}

View file

@ -32,6 +32,19 @@ enum UiAction {
}
}
pub fn get_real_mouse_pos(
rl: &RaylibHandle,
camera: &Camera
) -> Vector2
{
let mouse_pos = rl.get_mouse_position();
let top_left_bound = Vector2::new(camera.bounds.0.x, camera.bounds.1.y);
top_left_bound +
mouse_pos
.invert_for_canvas()
.scale_by(camera.canvas_to_real_factor)
}
/// entry point to handle UI inputs
pub fn collect_actions_from_inputs(
rl: &mut RaylibHandle,
@ -101,12 +114,7 @@ pub fn collect_actions_from_inputs(
actions.push(UiAction::NavigatePan(move_v));
}
let mouse_pos = rl.get_mouse_position();
let top_left_bound = Vector2::new(camera.bounds.0.x, camera.bounds.1.y);
let real_mouse_pos = top_left_bound +
mouse_pos
.invert_for_canvas()
.scale_by(camera.canvas_to_real_factor);
let real_mouse_pos = get_real_mouse_pos(rl, &camera);
let wheel_move = rl.get_mouse_wheel_move();
if wheel_move.abs() > 0.0 {
actions.push(UiAction::NavigateZoom {

View file

@ -1,7 +1,6 @@
use std::time::{SystemTime, UNIX_EPOCH};
use fully_pub::fully_pub;
use raylib::{color::Color, drawing::{RaylibDraw, RaylibDrawHandle}, math::Vector2};
#[derive(Debug)]
#[fully_pub]
@ -48,7 +47,7 @@ impl UiMessage {
UiMessage::new(UiMessageLevel::Error, text)
}
fn is_dead(&self) -> bool {
pub fn is_dead(&self) -> bool {
let current_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
@ -57,43 +56,3 @@ impl UiMessage {
(current_epoch - self.created_at) > self.ttl
}
}
pub fn render_messages(
messages: &Vec<UiMessage>,
draw_handle: &mut RaylibDrawHandle,
screen_def: &(i32, i32)
) -> () {
let mut vertical_offset: i32 = 0;
for message in messages.iter().rev() {
if message.is_dead() {
continue;
}
let text: &str = &message.text.to_string();
let padding = Vector2::new(10., 5.);
let pos: Vector2 = Vector2::new(
((screen_def.0/2) - 200) as f32,
(screen_def.1) as f32,
) + padding - Vector2::new(0., 40.0 + vertical_offset as f32);
draw_handle.draw_rectangle_v(
pos-padding,
Vector2::new(600.0 + 2.0*padding.x, 14.0 + 2.0*padding.y),
Color::WHITE
);
draw_handle.draw_text(
text,
pos.x as i32,
pos.y as i32,
16,
match message.level {
UiMessageLevel::Success => Color::GREEN,
UiMessageLevel::Warning => Color::ORANGE,
UiMessageLevel::Error => Color::RED,
_ => Color::GRAY
}
);
vertical_offset += 30;
}
}

View file

@ -1,4 +0,0 @@
/// takes the pre-processed data to be rendered and turn it into vectors instructions sent to raylib
fn map_render() {
}

View file

@ -0,0 +1,43 @@
use raylib::{color::Color, drawing::{RaylibDraw, RaylibDrawHandle}, math::Vector2};
use crate::ui::messages::{UiMessage, UiMessageLevel};
pub fn render_messages(
messages: &Vec<UiMessage>,
draw_handle: &mut RaylibDrawHandle,
screen_def: &(i32, i32)
) -> () {
let mut vertical_offset: i32 = 0;
for message in messages.iter().rev() {
if message.is_dead() {
continue;
}
let text: &str = &message.text.to_string();
let padding = Vector2::new(10., 5.);
let pos: Vector2 = Vector2::new(
((screen_def.0/2) - 200) as f32,
(screen_def.1) as f32,
) + padding - Vector2::new(0., 40.0 + vertical_offset as f32);
draw_handle.draw_rectangle_v(
pos-padding,
Vector2::new(600.0 + 2.0*padding.x, 14.0 + 2.0*padding.y),
Color::WHITE
);
draw_handle.draw_text(
text,
pos.x as i32,
pos.y as i32,
16,
match message.level {
UiMessageLevel::Success => Color::GREEN,
UiMessageLevel::Warning => Color::ORANGE,
UiMessageLevel::Error => Color::RED,
_ => Color::GRAY
}
);
vertical_offset += 30;
}
}

190
src/ui/renderer/mod.rs Normal file
View file

@ -0,0 +1,190 @@
mod messages;
use messages::render_messages;
use raylib::{color::Color, drawing::RaylibDraw, math::Vector2, RaylibHandle, RaylibThread};
use crate::{data::bbox_center, utils::{get_envelope_from_bounds, CanvasPos}, AppContext, UiMode};
use super::inputs::get_real_mouse_pos;
/// render entry point
/// takes the pre-processed data to be rendered and turn it into vectors instructions sent to raylib
pub fn render_app(
rl: &mut RaylibHandle,
thread: &RaylibThread,
context: AppContext
) {
let camera = context.camera;
let screen_def = (rl.get_screen_width(), rl.get_screen_height());
let mouse_pos = rl.get_mouse_position();
let real_mouse_pos = get_real_mouse_pos(&rl, &camera);
let mut d = rl.begin_drawing(&thread);
// d.draw_texture(&static_t, 0, 0, Color::WHITE);
d.clear_background(Color::BLACK);
// if let Some(texture) = tmp_texture {
// println!("{:?}", &texture);
// println!("center_canvas_pos {:?}", center_canvas_pos);
// d.draw_texture(texture, center_canvas_pos.x.ceil() as i32, center_canvas_pos.y.ceil() as i32, Color::WHITE);
// }
// render layers
let current_bounds_aabb = get_envelope_from_bounds(camera.bounds, 0.30);
{ // layers
let app_state_ref = context.state.lock().unwrap();
for (layer_i, layer) in context.layers.lock().unwrap().iter().enumerate() {
// Draw ways
if let Some(tree) = &layer.ways_tree {
for way in tree.locate_in_envelope_intersecting(&current_bounds_aabb) {
let mut last_p: Option<Vector2> = None;
for p in &way.positions {
if let Some(last_p_v) = last_p {
d.draw_line_ex(
last_p_v.to_canvas_pos(&camera),
p.to_canvas_pos(&camera),
1.0,
Color::BLUE
)
}
last_p = Some(*p);
}
let bbox_center_canvas = bbox_center(way.bbox).to_canvas_pos(&camera);
// draw name in center of polygon
if !way.name.is_empty() && (
(camera.zoom <= 0.9 && way.importance >= 10) ||
(camera.zoom >= 1.0 && way.importance >= 9) ||
camera.zoom >= 1.2
)
{
d.draw_text(
&way.name.to_string(),
bbox_center_canvas.x.ceil() as i32 - ((way.name.len()/2)*6) as i32,
bbox_center_canvas.y.ceil() as i32 - 5,
12,
Color::WHITE
);
}
}
}
// Draw POI points of interest
if let Some(tree) = &layer.pois_tree {
for point in tree.locate_in_envelope(&current_bounds_aabb) {
let canvas_pos = point.pos.to_canvas_pos(&camera);
let size = 2.0*(((1.)/(1.+ (-(0.4 * camera.zoom - 4.)).exp() )) + 0.2);
// check if object is selected.
let is_selected: bool =
if let Some(selected_elements) = app_state_ref.selected_elements_per_layer.get(layer_i) {
selected_elements.iter().any(|e_id| *e_id == point.id)
} else {
false
};
let line_thickness: f32 =
(match point.importance {
10 => 5.,
9 => 3.,
_ => 3.0
}) +
(if is_selected { 4.0 } else { 0. });
let color = if is_selected {
Color::YELLOW
} else {
match point.importance {
10 => Color::GREEN,
9 => Color::RED,
3 => Color::PURPLE,
_ => Color::new(255, 255, 255, 100)
}
};
d.draw_poly_lines_ex(
canvas_pos,
5,
match point.importance {
10 => size*19.,
9 => size*8.,
_ => size*3.0
},
0.,
line_thickness,
color
);
if
(camera.zoom <= 0.9 && point.importance >= 10) ||
(camera.zoom >= 1.0 && point.importance >= 9) ||
camera.zoom >= 1.2
{
d.draw_text(
&point.name.to_string(),
canvas_pos.x.ceil() as i32 + 8,
canvas_pos.y.ceil() as i32,
match point.importance {
10 => 17,
9 => 14,
_ => 12
},
Color::WHITE
);
}
}
}
}
}
// draw mouse cursor
d.draw_circle_sector_lines(mouse_pos, 10.0, 0.0, 360.0, 1, Color::MAGENTA);
{
// draw meta/debug info
let app_state_ref = context.state.lock().unwrap();
d.draw_text(&format!("Zoom: {:?}, Bounds: {:?}", camera.zoom, camera.bounds), 13, 12, 15, Color::WHITE);
d.draw_text(&format!("Mouse: {:?}", real_mouse_pos), 13, 30, 15, Color::WHITE);
d.draw_text(&format!("Center: {:?}", camera.center), 13, 45, 15, Color::WHITE);
// draw app mode
d.draw_text(
&format!("M: {}",
match app_state_ref.ui_mode {
UiMode::View => "View",
UiMode::Edit => "Edit"
})
,
12, screen_def.1 - 50,
15,
Color::WHITE
);
// draw loading indicator
if app_state_ref.loading {
// TODO: add a sprite
d.draw_circle_v(
Vector2::new(20., (screen_def.1-20) as f32),
10.0,
Color::WHITE
)
}
// draw UI messages and alerts
render_messages(
&app_state_ref.messages,
&mut d,
&screen_def
)
}
// draw target cross (center fixed cursor)
let cross_width = 6;
d.draw_line_v(
Vector2::new(((screen_def.0/2) - cross_width) as f32, (screen_def.1/2) as f32),
Vector2::new(((screen_def.0/2) + cross_width) as f32, (screen_def.1/2) as f32),
Color::WHITE
);
d.draw_line_v(
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) - cross_width) as f32),
Vector2::new((screen_def.0/2) as f32, ((screen_def.1/2) + cross_width) as f32),
Color::WHITE
);
}