Keyboard interaction with dialogues

main
Isaiah Odhner 2015-06-18 21:11:40 -04:00
parent 6c0324b1b6
commit 96f9f5442a
6 changed files with 151 additions and 41 deletions

View File

@ -64,7 +64,7 @@ You can also install it as a Chrome app.
* The Magnifier's viewport preview
* Shape styles on most of the shape tools
* The polygon tool needs some work
* Keyboard support in the menus and dialogues
* Keyboard support in the menus
* [This entire document full of things to do](TODO.md)
Clipboard support is somewhat limited.

View File

@ -20,6 +20,7 @@
* Issues
* Image > Flip/Rotate, Rotate by angle, ` ` Degrees sort of nullifies the image
* Crashes when saving large images
* If you open an image it resets the zoom but if you're on the magnification tool it doesn't update the options
* If you zoom in with the magnifier without previously changing the magnification on the toolbar,
@ -56,12 +57,6 @@
* Make the component ghost account for the window's titlebar
* Keyboard interaction with dialogues
* Close dialogues with Escape
* Navigating form windows
* Left/Right, Enter/Space
* Handle windows going off the screen
@ -296,7 +291,6 @@ Prankily wait for next user input before fullscreening and bluescreening
* Everything is in random files! "functions.js", REALLY?
* $Window has a $Button facility; $FormWindow overrides it with essentially a better one
* Image inversion code is duplicated in ChooserCanvas from tool-options.js but should go in image-manipulation.js
* `$w.$form.addClass("jspaint-horizontal").css({display: "flex"});`
* Comment everything and then try to make the code as obvious as the comments
* Improve code quality: https://codeclimate.com/github/1j01/jspaint

View File

@ -181,6 +181,7 @@
border-bottom: 1px solid #7b7b7b;
}
.jspaint-window-content .jspaint-dialogue-button:hover:active,
.jspaint-window-content .jspaint-dialogue-button.pressed,
.jspaint-tool:hover:active,
.jspaint-tool.selected {
padding-bottom: 2px;
@ -194,6 +195,7 @@
border-bottom: 1px solid white;
}
.jspaint-window-content .jspaint-dialogue-button:hover:active:before,
.jspaint-window-content .jspaint-dialogue-button.pressed:before,
.jspaint-tool:hover:active:before,
.jspaint-tool.selected:before {
top: 0px;
@ -396,6 +398,7 @@
border-bottom: 1px solid #808080;
}
.jspaint-button:hover:active,
.jspaint-button.pressed,
.jspaint-button.selected {
border-top: 1px solid #000;
border-left: 1px solid #000;
@ -403,6 +406,7 @@
border-bottom: 1px solid #fff;
}
.jspaint-button:hover:active:after,
.jspaint-button.pressed:after,
.jspaint-button.selected:after {
border-top: 1px solid #808080;
border-left: 1px solid #808080;
@ -413,7 +417,12 @@
right: 0px;
top: -3px;
}
.jspaint-button:hover:active:before {
.jspaint-button:hover:active:before,
.jspaint-button.pressed:before {
right: -1px;
top: -2px;
}
}
.jspaint-button:focus {
outline: 1px dotted black;
outline-offset: -4px;
}

View File

@ -205,12 +205,27 @@ html, body, .jspaint {
}
.jspaint-window-content .jspaint-button-group {
width: 85px;
display: flex;
flex: 0 0 auto;
flex-flow: column;
}
.jspaint-window-content .jspaint-button-group > button {
width: 95%;
width: 80px;
padding: 3px 5px;
}
.jspaint-window-content > form {
display: flex;
flex-flow: row;
}
.jspaint-dialogue-window .jspaint-window-content > form {
flex-flow: column;
}
.jspaint-dialogue-window .jspaint-window-content > form > .jspaint-button-group {
flex-flow: row;
}
.jspaint-dialogue-window .jspaint-window-content > form > div:first-child {
padding: 5px;
}
::before, ::after {
pointer-events: none;

View File

@ -1,6 +1,13 @@
$Window.Z_INDEX = 5;
// $.fn.isBefore = function(elem){
// if(!(elem instanceof $)){
// elem = $(elem);
// }
// return this.add(elem).index(elem) > 0;
// };
function $Window($component){
var $w = $(E("div")).addClass("jspaint-window").appendTo("body");
$w.$titlebar = $(E("div")).addClass("jspaint-window-titlebar").appendTo($w);
@ -30,6 +37,74 @@ function $Window($component){
});
});
$w.on("keydown", function(e){
if(e.ctrlKey || e.altKey || e.shiftKey){
return;
}
var $buttons = $w.$content.find("button.jspaint-button");
var $focused = $(document.activeElement);
var focused_index = $buttons.index($focused);
// if(focused_index === -1){
// if($focused.isBefore($buttons.first())){
// focused_index = 0;
// }else{
// focused_index = $buttons.length - 1;
// }
// }
// console.log(e.keyCode);
switch(e.keyCode){
case 40: // down
case 39: // right
if($focused.is("button")){
if(focused_index < $buttons.length - 1){
$buttons.get(focused_index + 1).focus();
e.preventDefault();
}
}
break;
case 38: // up
case 37: // left
if($focused.is("button")){
if(focused_index > 0){
$buttons.get(focused_index - 1).focus();
e.preventDefault();
}
}
break;
case 32: // space
case 13: // enter (doesn't actually work (in chrome), the button gets clicked immediately)
if($focused.is("button")){
$focused.addClass("pressed");
var release = function(){
$focused.removeClass("pressed");
$focused.off("focusout", release);
$(window).off("keyup", keyup);
};
var keyup = function(e){
if(e.keyCode === 32 || e.keyCode === 13){
release();
}
};
$focused.on("focusout", release);
$(window).on("keyup", keyup);
}
break;
case 9: // tab
// wrap around when tabbing through controls in a window
var $controls = $w.$content.find("input, textarea, select, button, a");
var focused_control_index = $controls.index($focused);
if(focused_control_index === $controls.length - 1){
e.preventDefault();
$controls[0].focus();
}
break;
case 27: // escape
$w.close();
break;
}
});
// @TODO: restore last focused controls when clicking/mousing down on the window
$w.applyBounds = function(){
$w.css({
left: Math.max(0, Math.min(innerWidth - $w.width(), $w[0].getBoundingClientRect().left)),
@ -69,9 +144,19 @@ function $Window($component){
}
});
// $w.updateTabIndexes = function(){
// var ti = 1;
// $w.find("button").each(function(){
// this.tabIndex = ti++;
// });
// $w.find("input, select").each(function(){
// this.tabIndex = ti++;
// });
// };
$w.$Button = function(text, handler){
$w.$content.append(
$(E("button"))
var $b = $(E("button"))
.appendTo($w.$content)
.addClass("jspaint-dialogue-button")
.text(text)
.on("click", function(){
@ -79,8 +164,9 @@ function $Window($component){
handler();
}
$w.close();
})
);
});
// $w.updateTabIndexes();
return $b;
};
$w.title = function(title){
if(title){
@ -116,12 +202,11 @@ function $FormWindow(title){
$w.title(title);
$w.$form = $form = $(E("form")).appendTo($w.$content);
$w.$form_left = $(E("div")).appendTo($w.$form);
$w.$form_right = $(E("div")).appendTo($w.$form).addClass("jspaint-button-group");
$w.$form.addClass("jspaint-horizontal").css({display: "flex"});
$w.$main = $(E("div")).appendTo($w.$form);
$w.$buttons = $(E("div")).appendTo($w.$form).addClass("jspaint-button-group");
$w.$Button = function(label, action){
var $b = $(E("button")).appendTo($w.$form_right).text(label);
var $b = $(E("button")).appendTo($w.$buttons).text(label);
$b.on("click", function(e){
// prevent the form from submitting
// @TODO: instead, prevent the form's submit event
@ -133,6 +218,12 @@ function $FormWindow(title){
// this should really not be needed @TODO
$b.addClass("jspaint-button jspaint-dialogue-button");
// $w.updateTabIndexes();
$b.on("mousedown", function(){
$b.focus();
});
return $b;
};

View File

@ -194,14 +194,14 @@ function are_you_sure(action){
if(saved){
action();
}else{
var $w = new $Window();
var $w = new $FormWindow().addClass("jspaint-dialogue-window");
$w.title("Paint");
$w.$content.text("Save changes to "+file_name+"?");
$w.$main.text("Save changes to "+file_name+"?");
$w.$Button("Save", function(){
$w.close();
file_save();
action();
});
}).focus();
$w.$Button("Discard", function(){
$w.close();
action();
@ -259,7 +259,7 @@ function paste(img){
paste_img();
$canvas_area.trigger("resize");
});
});
}).focus();
$w.$Button("Crop", function(){
paste_img();
});
@ -380,14 +380,14 @@ function render_history_as_gif(){
function undoable(callback, action){
saved = false;
if(redos.length > 5){
var $w = new $Window();
var $w = new $FormWindow().addClass("jspaint-dialogue-window");
$w.title("Paint");
$w.$content.html("Discard "+redos.length+" possible redo-able actions?<br>(Ctrl+Y or Ctrl+Shift+Z to redo)<br>");
$w.$main.html("Discard "+redos.length+" possible redo-able actions?<br>(Ctrl+Y or Ctrl+Shift+Z to redo)<br>");
$w.$Button(action ? "Discard and Apply" : "Discard", function(){
$w.close();
redos = [];
action && action();
});
}).focus();
$w.$Button("Keep", function(){
$w.close();
});
@ -527,8 +527,8 @@ function image_attributes(){
}
var $w = image_attributes.$window = new $FormWindow("Attributes");
var $form_left = $w.$form_left;
var $form_right = $w.$form_right;
var $main = $w.$main;
var $buttons = $w.$buttons;
// Information
@ -537,7 +537,7 @@ function image_attributes(){
"Size on disk": "Not available", // @TODO
"Resolution": "72 x 72 dots per inch",
};
var $table = $(E("table")).appendTo($form_left);
var $table = $(E("table")).appendTo($main);
for(var k in table){
var $tr = $(E("tr")).appendTo($table);
var $key = $(E("td")).appendTo($tr).text(k + ":");
@ -551,12 +551,12 @@ function image_attributes(){
var width_in_px = canvas.width;
var height_in_px = canvas.height;
var $width_label = $(E("label")).appendTo($form_left).text("Width:");
var $height_label = $(E("label")).appendTo($form_left).text("Height:");
var $width_label = $(E("label")).appendTo($main).text("Width:");
var $height_label = $(E("label")).appendTo($main).text("Height:");
var $width = $(E("input")).appendTo($width_label);
var $height = $(E("input")).appendTo($height_label);
$form_left.find("input")
$main.find("input")
.css({width: "40px"})
.on("change keyup keydown keypress mousedown mousemove paste drop", function(){
if($(this).is($width)){
@ -569,7 +569,7 @@ function image_attributes(){
// Fieldsets
var $units = $(E("fieldset")).appendTo($form_left).append('<legend>Transparency</legend>');
var $units = $(E("fieldset")).appendTo($main).append('<legend>Transparency</legend>');
$units.append('<label><input type="radio" name="units" value="in">Inches</label>');
$units.append('<label><input type="radio" name="units" value="cm">Cm</label>');
$units.append('<label><input type="radio" name="units" value="px">Pixels</label>');
@ -581,7 +581,7 @@ function image_attributes(){
current_unit = new_unit;
}).triggerHandler("change");
var $transparency = $(E("fieldset")).appendTo($form_left).append('<legend>Transparency</legend>');
var $transparency = $(E("fieldset")).appendTo($main).append('<legend>Transparency</legend>');
$transparency.append('<label><input type="radio" name="transparency" value="transparent">Transparent</label>');
$transparency.append('<label><input type="radio" name="transparency" value="opaque">Opaque</label>');
$transparency.find("[value=" + (transparency ? "transparent" : "opaque") + "]").attr({checked: true});
@ -601,7 +601,7 @@ function image_attributes(){
$canvas.trigger("user-resized", [0, 0, ~~width, ~~height]);
image_attributes.$window.close();
});
}).focus();
$w.$Button("Cancel", function(){
image_attributes.$window.close();
@ -622,7 +622,7 @@ function image_attributes(){
function image_flip_and_rotate(){
var $w = new $FormWindow("Flip and Rotate");
var $fieldset = $(E("fieldset")).appendTo($w.$form_left);
var $fieldset = $(E("fieldset")).appendTo($w.$main);
$fieldset.append("<legend>Flip or rotate</legend>");
$fieldset.append("<label><input type='radio' name='flip-or-rotate' value='flip-horizontal' checked/>Flip horizontal</label>");
$fieldset.append("<label><input type='radio' name='flip-or-rotate' value='flip-vertical'/>Flip vertical</label>");
@ -679,7 +679,7 @@ function image_flip_and_rotate(){
}
$w.close();
});
}).focus();
$w.$Button("Cancel", function(){
$w.close();
});
@ -690,9 +690,9 @@ function image_flip_and_rotate(){
function image_stretch_and_skew(){
var $w = new $FormWindow("Stretch and Skew");
var $fieldset_stretch = $(E("fieldset")).appendTo($w.$form_left);
var $fieldset_stretch = $(E("fieldset")).appendTo($w.$main);
$fieldset_stretch.append("<legend>Stretch</legend><table></table>");
var $fieldset_skew = $(E("fieldset")).appendTo($w.$form_left);
var $fieldset_skew = $(E("fieldset")).appendTo($w.$main);
$fieldset_skew.append("<legend>Skew</legend><table></table>");
var $RowInput = function($table, img_src, label_text, default_value, label_unit){
@ -727,7 +727,8 @@ function image_stretch_and_skew(){
var vskew = parseFloat(skew_y.val())/360*TAU;
stretch_and_skew(xscale, yscale, hskew, vskew);
$w.close();
});
}).focus();
$w.$Button("Cancel", function(){
$w.close();
});