Reputation: 1
I am trying to come up with a solution to this problem. I need to detect a mouse click on the little red rectangle for window closing. That clicked window should close only if there isn't some other window 'on top of it'.
I thought of detecting a click on red color, but that doesn't work really well.
PS. I can't use pop up windows, they need to be done like this.
Can someone help me with this? Thanks!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<input type="radio" name="choice" id="open">
Open new window:
Height = <input type="text" id="txt_height">, width =
<input type="text" id="txt_width"> <br>
<input type="radio" name="choice" id="close"> Closing window
<br> <br>
<canvas id="canvas" width="600" height="600" style="border: 1px solid black"></canvas> <br> <br>
Currently, there are <span id="details_1"></span> windows open. <br>
<span id="details_2"></span>
<script>
var A = 0;
var i = 0;
$("#details_1").text(i);
$('input[type=radio]').click(function(e) {
var value = $(this).val();
if(this.id === 'open') A=1;
if(this.id === 'close') A=2;
});
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
function mouse_position(canvas, event){
const rect = canvas.getBoundingClientRect();
const x = Math.floor(event.clientX - rect.left);
const y = Math.floor(event.clientY - rect.top);
if ( A === 1 ) open(x,y);
if ( A === 2 ) close();
}
$('#canvas').click(function(e){ mouse_position(canvas, event);})
function open(x,y){
i++;
var width = $("#txt_width").val();
var height = $("#txt_height").val();
var new_x = x + (width - 30);
if( !width.match(/^\d+$/) || !height.match(/^\d+$/)) alert('Natural numbers only!');
if ( height < 30) alert('Must be greater than 30!');
ctx.restore();
ctx.rect(x, y, width, height);
ctx.stroke();
ctx.save();
ctx.rect(x, y, width, 30);
ctx.fillStyle = "gray";
ctx.fillRect(x, y, width, 30);
ctx.font = "15px Arial";
ctx.fillStyle = "black";
ctx.fillText("Window no. " + i, x + 10, y + 20);
ctx.stroke();
ctx.save();
ctx.rect(new_x, y, 30, 30);
ctx.fillStyle = "red";
ctx.fillRect(new_x, y, 30, 30);
ctx.font = "15px Arial";
ctx.fillStyle = "black";
ctx.fillText("X", new_x + 10, y + 20);
ctx.stroke();
ctx.save();
$("#details_1").text(i);
$("#details_2").text('Those are: ');
for (var j = 0; j < i; j++)
$("#details_2").append('Window ' + (j+1) + ' ');
}
function close(){
//need help here
}
</script>
</body>
</html>
Upvotes: 0
Views: 223
Reputation: 2238
Here is a simpler example :
var canvas, ctx;
var win_list = [];
var A = 0;
var cnt = 0, i = 0;
function win (width, height) {
var x = 0, y = 0;
return {'id':++i, 'name':"Window no. "+i
,'open':open, 'show':show
,'isInside':isInside, 'isInsideClose':isInsideClose};
function open (xx,yy) {
x = xx; y = yy;
this.show();
return this;
}
function show () {
ctx.save();
ctx.fillStyle = 'white';
ctx.fillRect(x, y, width, height);
ctx.strokeStyle = 'black';
ctx.strokeRect(x, y, width, height);
ctx.fillStyle = 'gray';
ctx.fillRect(x, y, width, 30);
ctx.strokeStyle = 'black';
ctx.strokeRect(x, y, width, 30);
ctx.font = '15px Arial';
ctx.fillStyle = 'black';
ctx.fillText(this.name, x+10, y+20);
var new_x = x + (width - 30);
ctx.fillStyle = 'red';
ctx.fillRect(new_x, y, 30, 30);
ctx.strokeStyle = 'black';
ctx.strokeRect(new_x, y, 30, 30);
ctx.font = '15px Arial';
ctx.fillStyle = 'black';
ctx.fillText('X', new_x+10, y+20);
ctx.restore();
return this;
}
function isInside (xx, yy) {
return x <= xx && xx < x+width && y <= yy && yy < y+width;
}
function isInsideClose (xx, yy) {
var new_x = x + (width - 30);
return new_x <= xx && xx < x+width && y <= yy && yy < y+width;
}
}
function mouse_position (event) {
const rect = canvas.getBoundingClientRect();
const x = Math.floor(event.clientX - rect.left);
const y = Math.floor(event.clientY - rect.top);
if ( A === 1 ) create_win(x,y);
if ( A === 2 ) close(x,y);
}
function create_win (x, y) {
var width = $('#txt_width').val();
var height = $('#txt_height').val();
if (! width.match(/^\d+$/) || !height.match(/^\d+$/))
alert("Natural numbers only!");
if (height < 30)
alert("Must be greater than 30!");
var w = win(width, height).open(x, y);
win_list.push(w);
$("#details_1").text(win_list.length);
$("#details_2").append(' '+w.name);
}
function close (x, y) {
//need help here
var j;
for (j = win_list.length - 1; j >= 0 ; --j) {
let w = win_list[j];
if (w.isInside(x, y)) {
if (w.isInsideClose(x, y)) {
break;
}
return;
}
}
if (j < 0) return;
for (; j < win_list.length-1 ; ++j) {
win_list[j] = win_list[j+1];
}
win_list.pop();
redraw_all();
}
function redraw_all () {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, 600, 600);
win_list.forEach(function (w) {w.show();});
$("#details_1").text(win_list.length);
$("#details_2").text("Those are: ");
win_list.forEach(function (w) {
$("#details_2").append(' '+w.name);
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
Height = <input type="text" id="txt_height" value="100"><br>
Width = <input type="text" id="txt_width" value="200"><br>
<input type="radio" name="choice" id="open"> Open new window <br>
<input type="radio" name="choice" id="close"> Closing window <br>
<br>
<canvas id="canvas" width="600" height="600" style="border: 1px solid black"></canvas>
<br>
Currently, there are <span id="details_1"></span> windows open. <br>
<span id="details_2"></span>
<script>
canvas = document.getElementById('canvas');
ctx = canvas.getContext("2d");
$("#details_1").text(0);
$("#details_2").text("Those are: ");
$('input[type=radio]').click(function(e) {
if (this.id === 'open') A=1;
if (this.id === 'close') A=2;
});
$('#canvas').click(function(e){
mouse_position(e);
});
</script>
Upvotes: 1
Reputation: 54069
I would like to say its easy. No matter how you implement a windowing system it gets complicated very quickly.
Windows GUIs like the DOM are build as a tree, eg the desktop contains windows, windows contain title bars and the title bar contains the close icon.
In the example below I implement a very basic tree structure and various types of elements. As all elements share a lot of the same behavior and as I personal will not use the class syntax (until it is fixed properly) the elements are build add hock.
The click event searches the root element tree for clicked items.
Items are sorted visually with the focused window at the top.
If a window element is clicked and it is not in focus that window will be focused.
If a window element is clicked and it is in focus and the clicked item is a close button that window will be closed and the next in visual order will get focus.
If the top most item (canvas) is clicked a new window is created.
The top most element (root) is called desktopCanvas
. It is via this element that you open, close, focus, and query click events.
Normally all the actions would drive an event queue, but in the example I have not implemented such.
All items are built from the CanvasElement
and the CanvasElement
is assigned the properties and methods to implement a tree structure, basic rendering. You pass the Object type you want the element to be. Eg const element = new CanvasElement(new Area(0, 0, this.w, 30), MenuBar, undefined, name)
creates an element of type MenuBar
. You add it to a window via cWindow.add(element)
See example for more.
Click canvas to create windows. Click window to focus window. Click close on focused window to close. You can not close a window until it has focus.
const ctx = canvas.getContext("2d");
const canBounds = canvas.getBoundingClientRect();
canvas.addEventListener("click", mouseEvent);
function mouseEvent(event) {
const x = event.offsetX;
const y = event.offsetY;
const found = desktopCanvas.findClicked(x, y);
if (found === desktopCanvas) {
const wx = x < canvas.width - 200 ? x : canvas.width - 200;
const wy = y < canvas.height - 200 ? y : canvas.height - 200;
desktopCanvas.open(new CanvasElement(new Area(wx , wy, 200, 200), CanvasWindow, undefined, "Window"));
} else if (found) {
const top = found.topParent();
if (found.name === "close" && top.focused) { desktopCanvas.close(top) }
else { desktopCanvas.focus(top) }
}
}
const tree = {
get children() { return [] },
add(child, top = false) {
child.parent = this;
if (top) { this.children.unshift(child) }
else { this.children.push(child) }
},
remove(child) {
const idx = this.children.indexOf(child);
if (idx > -1) {
this.children.splice(idx, 1);
return true;
}
for (const c of this.children) {
if (c.remove(child)) { return true }
}
},
eachChild(cb, ...data) {
if (typeof cb === "string") {
for (const c of this.children) {c[cb](...data) }
return;
}
for (const c of this.children) { cb(c) }
},
topParent() {
var top = this;
while (top.top !== true) { top = top.parent }
return top;
},
}
function Area(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
function CanvasElement(area, type = {}, name, ...data) {
Object.assign(this, type);
name !== undefined && (this.name = name);
Object.assign(this, area);
Object.assign(this, tree);
this.init(...data);
}
CanvasElement.prototype = {
init() {},
draw(ctx) {
ctx.save();
this.transform(ctx);
this.drawBorder(ctx);
if (this.drawContent) { this.drawContent(ctx) }
this.eachChild("draw", ctx);
ctx.restore();
},
drawBorder(ctx) {
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.rect(0, 0, this.w, this.h);
ctx.stroke();
},
transform(ctx) { ctx.transform(1, 0, 0, 1, this.x, this.y) },
isInside(x, y) { return x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h },
findClicked(x, y) {
var idx = this.children.length;
if (this.isInside(x, y)) {
while (idx-- > 0) { // from top to bottom visually
const child = this.children[idx];
const found = child.findClicked(x - this.x, y - this.y);
if (found) { return found }
}
return this;
}
},
}
const CloseIcon = {
name : "close",
drawContent(ctx) {
const {w,h} = this;
ctx.fillStyle = "#f00";
ctx.fill();
ctx.font = "16px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = this.parent.parent.focused ? "#FFF" : "#000";
ctx.fillText("X", w / 2, h / 2);
},
}
const MenuBar = {
name : "menuBar",
init(text) {
this.text = text;
const bar = new Area(this.w - 18, 0, 18, 18);
this.add(new CanvasElement(bar, CloseIcon));
},
drawContent() {
ctx.fillStyle = this.parent.focused ? "#CCC" : "#999";
ctx.fill();
ctx.font = "16px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#000";
ctx.fillText(this.text, this.w / 2, this.h / 2 + 2);
},
}
const CanvasWindow = {
name: "window",
top: true,
init(name) {
this.name = name;
const bar = new Area(0, 0, this.w, 18);
this.add(new CanvasElement(bar, MenuBar, undefined, name))
},
drawContent() {
ctx.fillStyle = this.focused ? "#FFF" : "#EEE";
ctx.fill();
},
}
const Desktop = {
name: "desk",
focuse: undefined,
init(ctx) {
this.w = ctx.canvas.width;
this.h = ctx.canvas.height;
this.ctx = ctx;
},
draw() {
this.ctx.setTransform(1,0,0,1, 0, 0)
this.ctx.clearRect(0, 0, this.w, this.h);
if (this.drawContent) { this.drawContent(this.ctx) }
this.eachChild("draw", this.ctx);
},
close(item) {
this.remove(item);
if(this.focuse && this.focuse === item) {
if (this.children.length) { this.focus(this.children[this.children.length - 1]) }
else { this.focuse = undefined }
}
desktopCanvas.draw();
},
open(item) { this.focus(item) },
focus(item) {
this.remove(item);
this.add(item);
if(this.focuse) {
this.focuse.focused = false;
this.focuse = undefined;
}
item.focused = true;
this.focuse = item;
this.draw();
}
}
const desktopCanvas = new CanvasElement(new Area( 0, 0, canvas.width, canvas.width), Desktop, "desktop", ctx);
desktopCanvas.open(new CanvasElement(new Area(10 , 10, 200, 200), CanvasWindow, undefined, "Example window"));
<canvas id="canvas" width="600" height="600" style="border: 1px solid black"></canvas>
Click canvas to create windows. Click window to focus window. Click close on focused window to close. You can not close a window until it has focus.
Upvotes: 0