Implement zone dimension display and zone repositioning in editor
Feature #15 - Show Zone Dimensions: - Display real-time dimensions while drawing zones - Show width x height in both pixels and percentages - Display position coordinates (x, y) - Dimension label follows cursor with 15px offset - Label positioned to stay within monitor bounds - Automatically cleaned up when zone drawing completes Feature #16 - Move/Reposition Zones: - Click on existing zones to select and move them - Yellow highlight when zone is being moved (vs green for new zones) - Drag zones to new positions while maintaining size - Constrain movement to monitor bounds (can't move outside screen) - Real-time update of zone coordinates in data structure - Green color restored after move completes Editor Improvements: - Added zoneActors array to track zone widgets for moving - Updated instruction label to mention zone moving - Enhanced _findZoneAtPosition() helper to detect clicks on zones - Improved _removeLastZone() to properly clean up zone actors - Better cleanup in _cancelEditor() to prevent memory leaks - Track moving state (isMovingZone, movingZoneIndex, moveOffsetX/Y) UX Enhancements: - Separate visual feedback for creating (green) vs moving (yellow) zones - Smooth drag experience with proper offset tracking - Dimensions update in real-time during zone creation - Clear visual distinction between zone operations Fixes TODO items #15 and #16 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
26
TODO.md
26
TODO.md
@@ -43,22 +43,22 @@
|
|||||||
- [ ] Add UI to choose which layout to edit
|
- [ ] Add UI to choose which layout to edit
|
||||||
- [ ] Pre-populate editor with existing zones when editing
|
- [ ] Pre-populate editor with existing zones when editing
|
||||||
|
|
||||||
### 15. Show Zone Dimensions in Editor
|
### 15. Show Zone Dimensions in Editor ✅ COMPLETED
|
||||||
**Priority: Medium**
|
**Priority: Medium**
|
||||||
- [ ] Display zone dimensions while drawing in graphical editor
|
- [x] Display zone dimensions while drawing in graphical editor
|
||||||
- [ ] Show width and height in pixels and/or percentage
|
- [x] Show width and height in pixels and/or percentage
|
||||||
- [ ] Update dimensions in real-time as zone is resized
|
- [x] Update dimensions in real-time as zone is resized
|
||||||
- [ ] Display position coordinates (x, y)
|
- [x] Display position coordinates (x, y)
|
||||||
- [ ] Show dimensions near cursor or inside zone preview
|
- [x] Show dimensions near cursor or inside zone preview
|
||||||
|
|
||||||
### 16. Move/Reposition Zones in Editor
|
### 16. Move/Reposition Zones in Editor ✅ COMPLETED
|
||||||
**Priority: Medium**
|
**Priority: Medium**
|
||||||
- [ ] Allow clicking and dragging existing zones to move them
|
- [x] Allow clicking and dragging existing zones to move them
|
||||||
- [ ] Visual feedback when zone is selected for moving
|
- [x] Visual feedback when zone is selected for moving (yellow highlight)
|
||||||
- [ ] Snap to grid or guides (optional)
|
- [x] Prevent moving zones outside monitor bounds
|
||||||
- [ ] Prevent moving zones outside monitor bounds
|
- [x] Update zone coordinates as zone is moved
|
||||||
- [ ] Update zone coordinates as zone is moved
|
- [x] Maintain zone size while moving (only change position)
|
||||||
- [ ] Maintain zone size while moving (only change position)
|
- [ ] Snap to grid or guides (future enhancement)
|
||||||
|
|
||||||
## Code Quality & Robustness
|
## Code Quality & Robustness
|
||||||
|
|
||||||
|
|||||||
200
extension.js
200
extension.js
@@ -52,10 +52,16 @@ ZoneEditor.prototype = {
|
|||||||
this.editorOverlay = null;
|
this.editorOverlay = null;
|
||||||
this.isEditing = false;
|
this.isEditing = false;
|
||||||
this.zones = [];
|
this.zones = [];
|
||||||
|
this.zoneActors = []; // Track zone actors for moving
|
||||||
this.currentZone = null;
|
this.currentZone = null;
|
||||||
|
this.dimensionLabel = null;
|
||||||
this.startX = 0;
|
this.startX = 0;
|
||||||
this.startY = 0;
|
this.startY = 0;
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
|
this.isMovingZone = false;
|
||||||
|
this.movingZoneIndex = -1;
|
||||||
|
this.moveOffsetX = 0;
|
||||||
|
this.moveOffsetY = 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
startEditor: function() {
|
startEditor: function() {
|
||||||
@@ -82,7 +88,7 @@ ZoneEditor.prototype = {
|
|||||||
|
|
||||||
// Add instruction label
|
// Add instruction label
|
||||||
this.instructionLabel = new St.Label({
|
this.instructionLabel = new St.Label({
|
||||||
text: 'GridSnap Zone Editor\n\nDrag to draw zones | Ctrl+S to save | Ctrl+C to cancel | Delete to clear last zone',
|
text: 'GridSnap Zone Editor\n\nDrag to draw zones | Click zone to move | Ctrl+S to save | Ctrl+C to cancel | Delete to clear last zone',
|
||||||
style_class: 'gridsnap-editor-instructions',
|
style_class: 'gridsnap-editor-instructions',
|
||||||
x: 20,
|
x: 20,
|
||||||
y: 20
|
y: 20
|
||||||
@@ -152,51 +158,164 @@ ZoneEditor.prototype = {
|
|||||||
|
|
||||||
this.startX = x - monitor.x;
|
this.startX = x - monitor.x;
|
||||||
this.startY = y - monitor.y;
|
this.startY = y - monitor.y;
|
||||||
this.isDragging = true;
|
|
||||||
|
|
||||||
// Create zone preview
|
// Check if clicking on an existing zone to move it
|
||||||
this.currentZone = new St.Widget({
|
let clickedZoneIndex = this._findZoneAtPosition(this.startX, this.startY);
|
||||||
style_class: 'gridsnap-editor-zone',
|
|
||||||
x: this.startX,
|
if (clickedZoneIndex >= 0) {
|
||||||
y: this.startY,
|
// Start moving existing zone
|
||||||
width: 1,
|
this.isMovingZone = true;
|
||||||
height: 1
|
this.movingZoneIndex = clickedZoneIndex;
|
||||||
});
|
|
||||||
this.currentZone.set_style(
|
let zoneActor = this.zoneActors[clickedZoneIndex];
|
||||||
'border: 3px solid rgba(100, 255, 100, 0.9);' +
|
this.moveOffsetX = this.startX - zoneActor.x;
|
||||||
'background-color: rgba(100, 255, 100, 0.3);' +
|
this.moveOffsetY = this.startY - zoneActor.y;
|
||||||
'border-radius: 4px;'
|
|
||||||
);
|
// Highlight zone being moved
|
||||||
this.editorOverlay.add_child(this.currentZone);
|
zoneActor.set_style(
|
||||||
|
'border: 3px solid rgba(255, 255, 100, 0.9);' +
|
||||||
|
'background-color: rgba(255, 255, 100, 0.4);' +
|
||||||
|
'border-radius: 4px;'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Start creating new zone
|
||||||
|
this.isDragging = true;
|
||||||
|
|
||||||
|
// Create zone preview
|
||||||
|
this.currentZone = new St.Widget({
|
||||||
|
style_class: 'gridsnap-editor-zone',
|
||||||
|
x: this.startX,
|
||||||
|
y: this.startY,
|
||||||
|
width: 1,
|
||||||
|
height: 1
|
||||||
|
});
|
||||||
|
this.currentZone.set_style(
|
||||||
|
'border: 3px solid rgba(100, 255, 100, 0.9);' +
|
||||||
|
'background-color: rgba(100, 255, 100, 0.3);' +
|
||||||
|
'border-radius: 4px;'
|
||||||
|
);
|
||||||
|
this.editorOverlay.add_child(this.currentZone);
|
||||||
|
|
||||||
|
// Create dimension label
|
||||||
|
this.dimensionLabel = new St.Label({
|
||||||
|
text: '',
|
||||||
|
style_class: 'gridsnap-dimension-label'
|
||||||
|
});
|
||||||
|
this.dimensionLabel.set_style(
|
||||||
|
'color: white;' +
|
||||||
|
'font-size: 14px;' +
|
||||||
|
'background-color: rgba(0, 0, 0, 0.8);' +
|
||||||
|
'padding: 5px 10px;' +
|
||||||
|
'border-radius: 4px;'
|
||||||
|
);
|
||||||
|
this.editorOverlay.add_child(this.dimensionLabel);
|
||||||
|
}
|
||||||
|
|
||||||
return Clutter.EVENT_STOP;
|
return Clutter.EVENT_STOP;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_findZoneAtPosition: function(x, y) {
|
||||||
|
// Check if click is inside any existing zone
|
||||||
|
for (let i = 0; i < this.zoneActors.length; i++) {
|
||||||
|
let actor = this.zoneActors[i];
|
||||||
|
if (x >= actor.x && x <= actor.x + actor.width &&
|
||||||
|
y >= actor.y && y <= actor.y + actor.height) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
|
||||||
_onMotion: function(actor, event) {
|
_onMotion: function(actor, event) {
|
||||||
if (!this.isDragging || !this.currentZone) return Clutter.EVENT_PROPAGATE;
|
|
||||||
|
|
||||||
let [x, y] = event.get_coords();
|
let [x, y] = event.get_coords();
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
let endX = x - monitor.x;
|
if (this.isMovingZone) {
|
||||||
let endY = y - monitor.y;
|
// Moving existing zone
|
||||||
|
let newX = x - monitor.x - this.moveOffsetX;
|
||||||
|
let newY = y - monitor.y - this.moveOffsetY;
|
||||||
|
|
||||||
let rectX = Math.min(this.startX, endX);
|
let zoneActor = this.zoneActors[this.movingZoneIndex];
|
||||||
let rectY = Math.min(this.startY, endY);
|
|
||||||
let rectWidth = Math.abs(endX - this.startX);
|
|
||||||
let rectHeight = Math.abs(endY - this.startY);
|
|
||||||
|
|
||||||
this.currentZone.set_position(rectX, rectY);
|
// Constrain to monitor bounds
|
||||||
this.currentZone.set_size(rectWidth, rectHeight);
|
newX = Math.max(0, Math.min(newX, monitor.width - zoneActor.width));
|
||||||
|
newY = Math.max(0, Math.min(newY, monitor.height - zoneActor.height));
|
||||||
|
|
||||||
return Clutter.EVENT_STOP;
|
zoneActor.set_position(newX, newY);
|
||||||
|
|
||||||
|
// Update zone data
|
||||||
|
this.zones[this.movingZoneIndex].x = newX / monitor.width;
|
||||||
|
this.zones[this.movingZoneIndex].y = newY / monitor.height;
|
||||||
|
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isDragging && this.currentZone) {
|
||||||
|
// Creating new zone
|
||||||
|
let endX = x - monitor.x;
|
||||||
|
let endY = y - monitor.y;
|
||||||
|
|
||||||
|
let rectX = Math.min(this.startX, endX);
|
||||||
|
let rectY = Math.min(this.startY, endY);
|
||||||
|
let rectWidth = Math.abs(endX - this.startX);
|
||||||
|
let rectHeight = Math.abs(endY - this.startY);
|
||||||
|
|
||||||
|
this.currentZone.set_position(rectX, rectY);
|
||||||
|
this.currentZone.set_size(rectWidth, rectHeight);
|
||||||
|
|
||||||
|
// Update dimension label
|
||||||
|
if (this.dimensionLabel) {
|
||||||
|
let widthPx = Math.round(rectWidth);
|
||||||
|
let heightPx = Math.round(rectHeight);
|
||||||
|
let widthPct = Math.round((rectWidth / monitor.width) * 100);
|
||||||
|
let heightPct = Math.round((rectHeight / monitor.height) * 100);
|
||||||
|
|
||||||
|
this.dimensionLabel.set_text(
|
||||||
|
widthPx + 'x' + heightPx + 'px (' + widthPct + '% × ' + heightPct + '%)\n' +
|
||||||
|
'Position: ' + Math.round(rectX) + ', ' + Math.round(rectY)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Position label near cursor
|
||||||
|
this.dimensionLabel.set_position(
|
||||||
|
Math.min(endX + 15, monitor.width - 200),
|
||||||
|
Math.min(endY + 15, monitor.height - 60)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Clutter.EVENT_PROPAGATE;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onButtonRelease: function(actor, event) {
|
_onButtonRelease: function(actor, event) {
|
||||||
|
if (this.isMovingZone) {
|
||||||
|
// Finish moving zone
|
||||||
|
let zoneActor = this.zoneActors[this.movingZoneIndex];
|
||||||
|
|
||||||
|
// Reset highlight
|
||||||
|
zoneActor.set_style(
|
||||||
|
'border: 3px solid rgba(100, 255, 100, 0.9);' +
|
||||||
|
'background-color: rgba(100, 255, 100, 0.3);' +
|
||||||
|
'border-radius: 4px;'
|
||||||
|
);
|
||||||
|
|
||||||
|
this.isMovingZone = false;
|
||||||
|
this.movingZoneIndex = -1;
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isDragging) return Clutter.EVENT_PROPAGATE;
|
if (!this.isDragging) return Clutter.EVENT_PROPAGATE;
|
||||||
|
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
|
|
||||||
|
// Clean up dimension label
|
||||||
|
if (this.dimensionLabel) {
|
||||||
|
this.editorOverlay.remove_child(this.dimensionLabel);
|
||||||
|
this.dimensionLabel.destroy();
|
||||||
|
this.dimensionLabel = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentZone) {
|
if (this.currentZone) {
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
let width = this.currentZone.width;
|
let width = this.currentZone.width;
|
||||||
@@ -214,6 +333,9 @@ ZoneEditor.prototype = {
|
|||||||
|
|
||||||
this.zones.push(zone);
|
this.zones.push(zone);
|
||||||
|
|
||||||
|
// Track the zone actor for moving later
|
||||||
|
this.zoneActors.push(this.currentZone);
|
||||||
|
|
||||||
// Add zone number label
|
// Add zone number label
|
||||||
let label = new St.Label({
|
let label = new St.Label({
|
||||||
text: String(this.zones.length),
|
text: String(this.zones.length),
|
||||||
@@ -242,12 +364,21 @@ ZoneEditor.prototype = {
|
|||||||
_removeLastZone: function() {
|
_removeLastZone: function() {
|
||||||
if (this.zones.length > 0) {
|
if (this.zones.length > 0) {
|
||||||
this.zones.pop();
|
this.zones.pop();
|
||||||
|
|
||||||
|
// Also remove the last zone actor
|
||||||
|
if (this.zoneActors.length > 0) {
|
||||||
|
let removedActor = this.zoneActors.pop();
|
||||||
|
this.editorOverlay.remove_child(removedActor);
|
||||||
|
removedActor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
// Redraw editor
|
// Redraw editor
|
||||||
this._cancelEditor();
|
this._cancelEditor();
|
||||||
this.startEditor();
|
this.startEditor();
|
||||||
|
|
||||||
// Re-add existing zones
|
// Re-add existing zones
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
|
this.zoneActors = []; // Reset actors array
|
||||||
this.zones.forEach((zone, index) => {
|
this.zones.forEach((zone, index) => {
|
||||||
let zoneActor = new St.Widget({
|
let zoneActor = new St.Widget({
|
||||||
style_class: 'gridsnap-editor-zone',
|
style_class: 'gridsnap-editor-zone',
|
||||||
@@ -275,6 +406,9 @@ ZoneEditor.prototype = {
|
|||||||
);
|
);
|
||||||
zoneActor.add_child(label);
|
zoneActor.add_child(label);
|
||||||
this.editorOverlay.add_child(zoneActor);
|
this.editorOverlay.add_child(zoneActor);
|
||||||
|
|
||||||
|
// Track zone actors for moving
|
||||||
|
this.zoneActors.push(zoneActor);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -310,6 +444,15 @@ ZoneEditor.prototype = {
|
|||||||
_cancelEditor: function() {
|
_cancelEditor: function() {
|
||||||
if (!this.isEditing) return;
|
if (!this.isEditing) return;
|
||||||
|
|
||||||
|
// Clean up dimension label if it exists
|
||||||
|
if (this.dimensionLabel) {
|
||||||
|
if (this.editorOverlay) {
|
||||||
|
this.editorOverlay.remove_child(this.dimensionLabel);
|
||||||
|
}
|
||||||
|
this.dimensionLabel.destroy();
|
||||||
|
this.dimensionLabel = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.editorOverlay) {
|
if (this.editorOverlay) {
|
||||||
if (this.buttonPressId) this.editorOverlay.disconnect(this.buttonPressId);
|
if (this.buttonPressId) this.editorOverlay.disconnect(this.buttonPressId);
|
||||||
if (this.buttonReleaseId) this.editorOverlay.disconnect(this.buttonReleaseId);
|
if (this.buttonReleaseId) this.editorOverlay.disconnect(this.buttonReleaseId);
|
||||||
@@ -324,7 +467,10 @@ ZoneEditor.prototype = {
|
|||||||
|
|
||||||
this.isEditing = false;
|
this.isEditing = false;
|
||||||
this.zones = [];
|
this.zones = [];
|
||||||
|
this.zoneActors = [];
|
||||||
this.currentZone = null;
|
this.currentZone = null;
|
||||||
|
this.isMovingZone = false;
|
||||||
|
this.movingZoneIndex = -1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user