Keyboard interaction with dialogues
parent
6c0324b1b6
commit
96f9f5442a
|
@ -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.
|
||||
|
|
8
TODO.md
8
TODO.md
|
@ -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
|
||||
|
||||
|
|
13
classic.css
13
classic.css
|
@ -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;
|
||||
}
|
||||
|
|
19
layout.css
19
layout.css
|
@ -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;
|
||||
|
|
107
src/$Window.js
107
src/$Window.js
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue