feat(input): handle key sequence

This commit is contained in:
Matthieu Bessat 2024-07-13 11:26:55 +02:00
parent 2670e7d1da
commit 9e1ed1b6d5
4 changed files with 84 additions and 28 deletions

View file

@ -75,7 +75,7 @@ Logout with
You can upload your changeset,
Pressing `c`
Pressing the sequence `c c`
Your default text editor will appear, you can then preview the changes included in the changeset
@ -89,6 +89,10 @@ Your changeset will be then uploaded.
Depending on the result a sucess of failure message will appear at bottom center of your screen.
### Discard the changes
You can revert the changes using `c d`
## IPC with bobosm CLI
You can use bobosm CLI to connect to a existing graphical instance of bobosm.

View file

@ -18,7 +18,9 @@
- [x] 2024-07-01: upload changeset
- [x] 2024-07-01: add success or error messages buffer
- [x] 2024-07-01: refacto with clippy
- [ ] refacto renderer
- [x] refacto renderer
- [ ] compute the real zoom
- [ ] hint key to select nodes
- [ ] vim-like commands sequence
- [ ] show every singular nodes
- [ ] create node

View file

@ -18,14 +18,13 @@ 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::{UiMessage, UiMessageLevel}, renderer::render_app, selection::get_objects_in_area};
use ui::{changeset::try_changeset, 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 ureq::Agent;
use rand::Rng;
use crate::utils::Invertion;
use crate::utils::ToSlice;
pub const USER_AGENT: &str = "BobOSM dev";
@ -40,12 +39,20 @@ struct Camera {
canvas_to_real_factor: f32
}
#[fully_pub]
#[derive(Debug)]
struct InputBuffer {
/// unhandled keys typed in a sequence so far
key_sequence: Vec<KeyboardKey>,
start_time: f64
}
#[fully_pub]
struct AppContext {
/// snapshot of camera state
camera: Camera,
state: Arc<Mutex<AppState>>,
layers: Arc<Mutex<Vec<Layer>>>
layers: Arc<Mutex<Vec<Layer>>>,
}
#[derive(PartialEq, Debug)]
@ -62,7 +69,8 @@ struct AppState {
loading: bool,
messages: Vec<UiMessage>,
selected_elements_per_layer: Vec<Vec<i64>>,
ui_mode: UiMode
ui_mode: UiMode,
input_buffer: InputBuffer
}
#[derive(Debug)]
@ -183,6 +191,10 @@ fn main() -> Result<()> {
),
];
let app_state = Arc::new(Mutex::new(AppState {
input_buffer: InputBuffer {
start_time: 0.0,
key_sequence: vec![]
},
loading: false,
messages,
selected_elements_per_layer: vec![],
@ -229,8 +241,8 @@ fn main() -> Result<()> {
let mut screen_def: (i32, i32) = (rl.get_screen_width(), rl.get_screen_height());
// let target_geo: Vector2 = Vector2::new(1.33, 49.175); // aubevoye
let target_geo: Vector2 = Vector2::new(1.3355, 49.1761); // aubevoye boulangerie
// let target_geo: Vector2 = Vector2::new(1.33, 49.159); // gaillon
// let target_geo: Vector2 = Vector2::new(1.3355, 49.1761); // aubevoye boulangerie
let target_geo: Vector2 = Vector2::new(1.33, 49.159); // gaillon
// let target_geo: Vector2 = Vector2::new(4.86101, 45.74803); // lyon univ3 tabac
let target_zoom: f32 = 50.;
@ -250,9 +262,8 @@ fn main() -> Result<()> {
break;
}
if screen_def.0 != rl.get_screen_width() || screen_def.1 != rl.get_screen_height() {
println!("Screen definition changed");
screen_def = (rl.get_screen_width(), rl.get_screen_height());
println!("New screen def: {:?}", screen_def);
println!("Screen def changed. New def: {:?}", screen_def);
// compute new bounds
camera.bounds = compute_bounds_from_center_zoom(screen_def, camera.center, target_zoom);
@ -260,13 +271,11 @@ fn main() -> Result<()> {
app_state_ref.messages.push(UiMessage::success("Screen def changed"));
}
// compute mouse position
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 actions = collect_actions_from_inputs(&mut rl, &camera, &app_state.lock().unwrap());
let actions = collect_actions_from_inputs(&mut rl, AppContext {
state: app_state.clone(),
layers: layers.clone(),
camera: camera.clone(),
});
// handle asked actions
for action in actions {
match action {
@ -290,7 +299,6 @@ fn main() -> Result<()> {
app_state_mut.messages.push(
UiMessage::error(&format!("Zone too big to download"))
);
} else {
spawn_download_thread(
app_state.clone(),
@ -339,6 +347,10 @@ fn main() -> Result<()> {
let res = edit_tags(&xdg_dirs, app_state.clone(), layers.clone());
println!("{:?}", res);
},
UiAction::ChangesetPrepare => {
println!("Changeset prepare");
let _ = try_changeset(&xdg_dirs, layers.clone(), &osm_client);
},
_ => {
println!("Warn: unhandled action");
},
@ -349,14 +361,14 @@ fn main() -> Result<()> {
camera.center = camera.bounds.1.lerp(camera.bounds.0, 0.5);
camera.real_to_canvas_factor = (rl.get_screen_width() as f32) / (camera.bounds.0.x - camera.bounds.1.x).abs();
camera.canvas_to_real_factor = 1.0/camera.real_to_canvas_factor;
camera.zoom = 1.0/(camera.bounds.0.x - camera.bounds.1.x).abs();
// TODO: remap to the real distance formula
camera.map_zoom = 4.680_851*camera.zoom;
render_app(&mut rl, &thread, AppContext {
state: app_state.clone(),
layers: layers.clone(),
camera: camera.clone()
camera: camera.clone(),
})
}
Ok(())

View file

@ -2,10 +2,10 @@ use fully_pub::fully_pub;
use raylib::{ffi::{KeyboardKey, MouseButton}, math::Vector2, RaylibHandle};
use rstar::AABB;
use crate::{utils::{Invertion, ToAABB}, AppState, Camera, UiMode};
use crate::{utils::{Invertion, ToAABB}, AppContext, AppState, Camera, UiMode};
/// possible ui actions
#[derive(Debug)]
#[derive(Debug, Clone)]
#[fully_pub]
enum UiAction {
AppExit,
@ -32,6 +32,12 @@ enum UiAction {
}
}
const SEQUENCE_MAP: &'static [(&[KeyboardKey], &UiAction)] = &[
(&[KeyboardKey::KEY_C, KeyboardKey::KEY_C], &UiAction::ChangesetPrepare),
(&[KeyboardKey::KEY_N, KeyboardKey::KEY_N], &UiAction::CreateNode),
(&[KeyboardKey::KEY_N, KeyboardKey::KEY_W], &UiAction::CreateWay)
];
pub fn get_real_mouse_pos(
rl: &RaylibHandle,
camera: &Camera
@ -45,18 +51,51 @@ pub fn get_real_mouse_pos(
.scale_by(camera.canvas_to_real_factor)
}
fn handle_key_sequence(
rl: &mut RaylibHandle,
app_state: &mut AppState
) -> Vec<UiAction> {
let first_keys: Vec<KeyboardKey> = SEQUENCE_MAP
.iter()
.map(|x| x.0.get(0).unwrap().clone())
.collect();
// handle vim-like key sequence
if let Some(key_pressed) = rl.get_key_pressed() {
// println!("elapsed time {:?}", rl.get_time());
if (app_state.input_buffer.start_time - rl.get_time()).abs() > 2. {
app_state.input_buffer.key_sequence = vec![];
}
if app_state.input_buffer.key_sequence.is_empty() && !first_keys.contains(&key_pressed) {
return vec![];
}
app_state.input_buffer.start_time = rl.get_time();
app_state.input_buffer.key_sequence.push(key_pressed);
println!("Sequence so far: {:?}", &app_state.input_buffer.key_sequence);
for (sequence_keys, sequence_action) in SEQUENCE_MAP.iter() {
if app_state.input_buffer.key_sequence == *sequence_keys {
app_state.input_buffer.key_sequence = vec![];
return vec![(**sequence_action).clone()];
}
}
}
return vec![];
}
/// entry point to handle UI inputs
pub fn collect_actions_from_inputs(
rl: &mut RaylibHandle,
camera: &Camera,
app_state: &AppState
context: AppContext
) -> Vec<UiAction> {
let mut app_state = context.state.lock().unwrap();
let camera = context.camera;
let mut actions: Vec<UiAction> = vec![];
let mut pan_f = 0.1;
let mut zoom_f = 1.0;
if rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT) {
pan_f = 0.33;
zoom_f = 3.0;
zoom_f = 5.0;
}
if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) {
pan_f = 0.01;
@ -79,9 +118,6 @@ pub fn collect_actions_from_inputs(
if rl.is_key_down(KeyboardKey::KEY_Q) {
actions.push(UiAction::AppExit);
}
if rl.is_key_pressed(KeyboardKey::KEY_C) {
actions.push(UiAction::ChangesetPrepare);
}
if rl.is_key_pressed(KeyboardKey::KEY_E) {
actions.push(UiAction::ToggleEditMode);
}
@ -142,6 +178,8 @@ pub fn collect_actions_from_inputs(
.invert_for_canvas();
actions.push(UiAction::NavigatePan(mouse_move));
}
actions.extend(handle_key_sequence(rl, &mut app_state));
actions
}