Skip to content

Commit

Permalink
Merge pull request ArturKovacs#164 from ArturKovacs/camera-keys
Browse files Browse the repository at this point in the history
Allow controlling the camera with the keyboard
  • Loading branch information
ArturKovacs authored Jan 1, 2021
2 parents 2e692c1 + ec3c560 commit fc64c8f
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ linux_mime_types = [
winres = "0.1"

[dependencies]
gelatin = { path = "./subcrates/gelatin", version = "0.5" }
gelatin = { path = "./subcrates/gelatin", version = "0.6" }
ureq = { version = "1", features = ["json"], optional = true }
lazy_static = "1.4.0"
directories-next = "2.0"
Expand Down
13 changes: 11 additions & 2 deletions src/input_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ pub static PLAY_PRESENT_NAME: &str = "play_present";
pub static PLAY_PRESENT_RND_NAME: &str = "play_present_rnd";
pub static TOGGLE_ANTIALIAS_NAME: &str = "toggle_antialias";
pub static SET_AUTOMATIC_ANTIALIAS_NAME: &str = "automatic_antialias";
pub static ZOOM_IN_NAME: &str = "zoom_in";
pub static ZOOM_OUT_NAME: &str = "zoom_out";
pub static PAN_LEFT_NAME: &str = "pan_left";
pub static PAN_RIGHT_NAME: &str = "pan_right";
pub static PAN_UP_NAME: &str = "pan_up";
pub static PAN_DOWN_NAME: &str = "pan_down";

lazy_static! {
pub static ref DEFAULT_BINDINGS: HashMap<&'static str, Vec<&'static str>> = {
Expand All @@ -46,11 +52,13 @@ lazy_static! {
}

pub fn char_to_input_key(ch: char) -> String {
let mut input_key = String::with_capacity(5);
let mut input_key = String::with_capacity(8);
if ch == ' ' {
input_key.push_str("space");
} else if ch == '+' {
input_key.push_str("add");
} else if ch == '-' {
input_key.push_str("subtract");
} else {
input_key.push(ch);
}
Expand Down Expand Up @@ -154,7 +162,8 @@ pub fn action_triggered(
if let Some(Some(keys)) = bindings.map(|b| b.get(action_name)) {
keys_triggered(keys.as_slice(), input_key, modifiers)
} else {
let keys = DEFAULT_BINDINGS.get(action_name).unwrap();
let empty = Vec::new();
let keys = DEFAULT_BINDINGS.get(action_name).unwrap_or(&empty);
keys_triggered(keys.as_slice(), input_key, modifiers)
}
}
8 changes: 6 additions & 2 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ pub fn virtual_keycode_is_char(vk: VirtualKeyCode) -> bool {
VirtualKeyCode::Z => true,
VirtualKeyCode::Space => true,
VirtualKeyCode::Caret => true,
VirtualKeyCode::Numlock => true,
VirtualKeyCode::Numpad0 => true,
VirtualKeyCode::Numpad1 => true,
VirtualKeyCode::Numpad2 => true,
Expand Down Expand Up @@ -199,7 +198,12 @@ pub fn virtual_keycode_to_string(vk: VirtualKeyCode) -> String {
VirtualKeyCode::Mail => "Mail".into(),
VirtualKeyCode::MediaSelect => "MediaSelect".into(),
VirtualKeyCode::MediaStop => "MediaStop".into(),
VirtualKeyCode::Minus => "Minus".into(),

// Handling the alphanumeric and the numpad version of `-`
// as the same key.
// Alphanumeric is `Minus`, the numapd is `Subtract`
VirtualKeyCode::Minus => "Subtract".into(),

VirtualKeyCode::Multiply => "Multiply".into(),
VirtualKeyCode::Mute => "Mute".into(),
VirtualKeyCode::MyComputer => "MyComputer".into(),
Expand Down
191 changes: 177 additions & 14 deletions src/widgets/picture_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

use gelatin::cgmath::{Matrix4, Vector3};
use gelatin::cgmath::{Matrix4, Vector2, Vector3};
use gelatin::glium::glutin::event::{ElementState, ModifiersState, MouseButton};
use gelatin::glium::{
program, uniform, uniforms::MagnifySamplerFilter, Display, Frame, Program, Surface,
Expand Down Expand Up @@ -41,6 +41,22 @@ pub enum ScalingMode {
FitMin,
}

#[derive(PartialEq, Eq, Clone, Copy)]
pub enum MovementDir {
None,
Positive,
Negative,
}

impl MovementDir {
fn moving(self) -> bool {
match self {
MovementDir::None => false,
_ => true,
}
}
}

#[derive(Debug, Clone)]
enum HoverState {
None,
Expand Down Expand Up @@ -122,12 +138,23 @@ struct PictureWidgetData {
img_pos: LogicalVector,
antialiasing: Antialias,

hor_pan_input: MovementDir,
ver_pan_input: MovementDir,
zoom_input: MovementDir,
/// The velocity of horizontal panning
hor_pan_vel: f32,
/// The velocity of vertical panning
ver_pan_vel: f32,
/// The velocity of zooming
zoom_vel: f32,

last_click_time: Instant,
last_mouse_pos: LogicalVector,
panning: bool,
hover_state: HoverState,

first_draw: bool,
last_cam_move_time: Instant,
next_update: NextUpdate,
bottom_bar: Rc<BottomBar>,
left_to_pan_hint: Rc<HelpScreen>,
Expand All @@ -146,7 +173,7 @@ impl WidgetData for PictureWidgetData {
}
}
impl PictureWidgetData {
fn fit_image_to_panel(&mut self, _display: &Display, dpi_scale: f32, stretch: bool) {
fn fit_image_to_panel(&mut self, dpi_scale: f32, stretch: bool) {
let size = self.drawn_bounds.size.vec;
if let Some(texture) = self.get_texture() {
let panel_aspect = size.x / size.y;
Expand Down Expand Up @@ -180,7 +207,9 @@ impl PictureWidgetData {
}
}

fn zoom_image(&mut self, anchor: LogicalVector, mut image_texel_size: f32) {
fn zoom_image(&mut self, anchor: LogicalVector, mut delta: f32) {
delta = if delta > 0.0 { delta + 1.0 } else { 1.0 / (delta.abs() + 1.0) };
let mut image_texel_size = (self.img_texel_size * delta).max(0.0);
if (image_texel_size - 1.0).abs() < 0.01 {
image_texel_size = 1.0;
} else if image_texel_size < MIN_ZOOM_FACTOR {
Expand All @@ -195,23 +224,88 @@ impl PictureWidgetData {
self.render_validity.invalidate();
}

fn update_image_transform(&mut self, display: &Display, dpi_scale: f32) {
fn update_image_transform(&mut self, dpi_scale: f32) {
match self.scaling {
ScalingMode::Fixed => {
let center_offset = (self.drawn_bounds.size - self.prev_draw_size) * 0.5f32;
self.img_pos += center_offset;
self.apply_img_bounds(dpi_scale);
}
ScalingMode::FitStretch => {
self.fit_image_to_panel(display, dpi_scale, true);
self.fit_image_to_panel(dpi_scale, true);
}
ScalingMode::FitMin => {
self.fit_image_to_panel(display, dpi_scale, false);
self.fit_image_to_panel(dpi_scale, false);
}
}
self.prev_draw_size = self.drawn_bounds.size;
}

fn apply_camera_movement(&mut self, dpi_scale: f32) {
fn animate_value(v: &mut f32, dir: f32, dt: f32, next_update: &mut NextUpdate) {
#[allow(clippy::float_cmp)]
if v.signum() != dir {
*v = 0.0;
}
*v += dir * dt * (2.0 / (v.abs() + 1.0));
*next_update = NextUpdate::Soonest;
}

let now = Instant::now();
let dt_sec = now.duration_since(self.last_cam_move_time).as_secs_f32();
self.last_cam_move_time = now;

match self.hor_pan_input {
MovementDir::None => self.hor_pan_vel = 0.0,
MovementDir::Positive => {
animate_value(&mut self.hor_pan_vel, 1.0, dt_sec, &mut self.next_update)
}
MovementDir::Negative => {
animate_value(&mut self.hor_pan_vel, -1.0, dt_sec, &mut self.next_update)
}
}
match self.ver_pan_input {
MovementDir::None => self.ver_pan_vel = 0.0,
MovementDir::Positive => {
animate_value(&mut self.ver_pan_vel, 1.0, dt_sec, &mut self.next_update)
}
MovementDir::Negative => {
animate_value(&mut self.ver_pan_vel, -1.0, dt_sec, &mut self.next_update)
}
}
match self.zoom_input {
MovementDir::None => self.zoom_vel = 0.0,
MovementDir::Positive => {
animate_value(&mut self.zoom_vel, 1.0, dt_sec, &mut self.next_update)
}
MovementDir::Negative => {
animate_value(&mut self.zoom_vel, -1.0, dt_sec, &mut self.next_update)
}
}

if self.zoom_input.moving() {
let bounds_size = self.drawn_bounds.size.vec;
let anchor = LogicalVector::new(bounds_size.x * 0.5, bounds_size.y * 0.5);
self.zoom_image(anchor, self.zoom_vel * dt_sec);
}
if self.hor_pan_input.moving() || self.ver_pan_input.moving() {
let panning_speed = 400.0 * dpi_scale;
let pos_delta = Vector2::new(self.hor_pan_vel, self.ver_pan_vel) * dt_sec;
self.scaling = ScalingMode::Fixed;
self.update_scaling_buttons();
self.img_pos.vec += panning_speed * pos_delta;
}
}

fn camera_movement_will_start(&mut self) {
// If there hasn't been any movement in a while, then reset the last update time
// to avoid large jumps at the beggining of a move when the delta would be large.
if !self.hor_pan_input.moving() && !self.ver_pan_input.moving() && !self.zoom_input.moving()
{
self.last_cam_move_time = Instant::now();
}
}

fn set_window_title_filename(
&self,
window: &Window,
Expand Down Expand Up @@ -384,10 +478,17 @@ impl PictureWidget {
scaling,
img_pos: Default::default(),
antialiasing,
hor_pan_input: MovementDir::None,
ver_pan_input: MovementDir::None,
zoom_input: MovementDir::None,
hor_pan_vel: 0.0,
ver_pan_vel: 0.0,
zoom_vel: 0.0,
last_click_time: Instant::now() - Duration::from_secs(10),
last_mouse_pos: Default::default(),
panning: false,
hover_state: HoverState::None,
last_cam_move_time: Instant::now(),
first_draw: true,
next_update: NextUpdate::Latest,
bottom_bar,
Expand Down Expand Up @@ -554,6 +655,7 @@ impl Widget for PictureWidget {
data.next_update = NextUpdate::Soonest;
return data.next_update;
}
let now = Instant::now();
let prev_texture = data.playback_manager.image_texture();
data.next_update = data.playback_manager.update_image(window);
let new_texture = data.playback_manager.image_texture();
Expand Down Expand Up @@ -584,11 +686,14 @@ impl Widget for PictureWidget {
}
data.clipboard_request_was_pending = request_pending;
} else if request_pending {
let now = Instant::now();
let next_update = now + Duration::from_millis(100);
data.next_update = data.next_update.aggregate(NextUpdate::WaitUntil(next_update));
}
}
if data.zoom_input.moving() || data.hor_pan_input.moving() || data.ver_pan_input.moving() {
data.render_validity.invalidate();
data.next_update = NextUpdate::Soonest;
}
let next_copy_noti_update = data.copy_notifications.update();
data.next_update = data.next_update.aggregate(next_copy_noti_update);
data.next_update
Expand All @@ -601,7 +706,8 @@ impl Widget for PictureWidget {
if !data.visible {
return Ok(data.next_update);
}
data.update_image_transform(context.display, context.dpi_scale_factor);
data.update_image_transform(context.dpi_scale_factor);
data.apply_camera_movement(context.dpi_scale_factor);
texture = data.get_texture();
}
{
Expand Down Expand Up @@ -759,20 +865,16 @@ impl Widget for PictureWidget {
EventKind::MouseScroll { delta } => {
let mut borrowed = self.data.borrow_mut();
let delta = delta.vec.y * 0.375;
let delta = if delta > 0.0 { delta + 1.0 } else { 1.0 / (delta.abs() + 1.0) };

let new_image_texel_size = (borrowed.img_texel_size * delta).max(0.0);

borrowed.zoom_image(event.cursor_pos, new_image_texel_size);
borrowed.zoom_image(event.cursor_pos, delta);
}
EventKind::ReceivedCharacter(ch) => {
//println!("Got char {}", ch);
// When the control key is held down, this character is going to be the keycode
// of an ascii control character
// See https://en.wikipedia.org/wiki/Caret_notation
if !event.modifiers.ctrl() {
//println!("triggering for char {}", ch);
let input_key = char_to_input_key(ch);
//println!("triggering for char {}, input str: {}", ch, input_key);
self.handle_key_input(input_key.as_str(), event.modifiers);
}
}
Expand All @@ -795,6 +897,67 @@ impl Widget for PictureWidget {
) {
borrowed.panning = input.state == ElementState::Pressed;
}

let pressed = input.state == ElementState::Pressed;

macro_rules! movement_trigger {
($input:expr, $vel:expr, $name:expr, $dir:expr) => {
if action_triggered(
&borrowed.configuration,
$name,
input_key_str.as_str(),
event.modifiers,
) {
if $input == $dir && !pressed {
$input = MovementDir::None;
$vel = 0.0;
}
if $input != $dir && pressed {
borrowed.camera_movement_will_start();
$input = $dir;
}
}
};
}

movement_trigger!(
borrowed.zoom_input,
borrowed.zoom_vel,
ZOOM_IN_NAME,
MovementDir::Positive
);
movement_trigger!(
borrowed.zoom_input,
borrowed.zoom_vel,
ZOOM_OUT_NAME,
MovementDir::Negative
);

movement_trigger!(
borrowed.hor_pan_input,
borrowed.hor_pan_vel,
PAN_LEFT_NAME,
MovementDir::Positive
);
movement_trigger!(
borrowed.hor_pan_input,
borrowed.hor_pan_vel,
PAN_RIGHT_NAME,
MovementDir::Negative
);

movement_trigger!(
borrowed.ver_pan_input,
borrowed.ver_pan_vel,
PAN_UP_NAME,
MovementDir::Positive
);
movement_trigger!(
borrowed.ver_pan_input,
borrowed.ver_pan_vel,
PAN_DOWN_NAME,
MovementDir::Negative
);
}
}
EventKind::DroppedFile(ref path) => {
Expand Down
2 changes: 1 addition & 1 deletion subcrates/gelatin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gelatin"
version = "0.5.0"
version = "0.6.0"
description = "A basic UI framework"
repository = "https://github.com/ArturKovacs/emulsion"
authors = ["Artur Barnabas <kovacs.artur.barnabas@gmail.com>"]
Expand Down
Loading

0 comments on commit fc64c8f

Please sign in to comment.