Add ability to edit existing custom layouts
Implemented layout editing functionality with selection UI: Features: - New layout selector dialog when pressing Super+Shift+E - Lists all existing custom layouts with zone counts - "Create New Layout" option for new layouts - Click any layout to edit it in the zone editor - Existing zones pre-populated when editing - Updates existing layout when saving (shows "updated" message) - ESC key or Cancel button to close selector Implementation details: - Added editingLayoutId and editingLayoutName properties to track edit mode - Modified startEditor() to accept optional layoutId and layoutData parameters - Created showLayoutSelector() method to display layout selection UI - Created _createZoneActorsFromData() to pre-populate zones when editing - Updated _saveLayout() to update existing layout vs create new - Changed keybinding to call showLayoutSelector() instead of startEditor() - Added _closeLayoutSelector() for cleanup - Deep copy of zones array to avoid reference issues User experience: - Press Super+Shift+E to open layout selector - Choose existing layout to edit or create new - Edit zones (move, resize, add, delete) - Save updates existing layout or creates new - Clear visual feedback for edit vs create mode Fixes TODO item #5. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
10
TODO.md
10
TODO.md
@@ -36,12 +36,12 @@
|
|||||||
- [ ] Verify zone calculations work correctly with display scaling
|
- [ ] Verify zone calculations work correctly with display scaling
|
||||||
- [ ] May need to use `get_resource_scale()` or similar
|
- [ ] May need to use `get_resource_scale()` or similar
|
||||||
|
|
||||||
### 5. Edit Existing Custom Layouts
|
### 5. Edit Existing Custom Layouts ✅ COMPLETED
|
||||||
**Priority: Medium**
|
**Priority: Medium**
|
||||||
- [ ] Currently can only create new layouts in editor
|
- [x] Currently can only create new layouts in editor
|
||||||
- [ ] Add ability to select and edit existing custom layouts
|
- [x] Add ability to select and edit existing custom layouts
|
||||||
- [ ] Add UI to choose which layout to edit
|
- [x] Add UI to choose which layout to edit
|
||||||
- [ ] Pre-populate editor with existing zones when editing
|
- [x] Pre-populate editor with existing zones when editing
|
||||||
|
|
||||||
### 15. Show Zone Dimensions in Editor ✅ COMPLETED
|
### 15. Show Zone Dimensions in Editor ✅ COMPLETED
|
||||||
**Priority: Medium**
|
**Priority: Medium**
|
||||||
|
|||||||
259
extension.js
259
extension.js
@@ -66,14 +66,193 @@ ZoneEditor.prototype = {
|
|||||||
this.resizingZoneIndex = -1;
|
this.resizingZoneIndex = -1;
|
||||||
this.resizeEdge = null; // 'n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'
|
this.resizeEdge = null; // 'n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'
|
||||||
this.originalZoneBounds = null;
|
this.originalZoneBounds = null;
|
||||||
|
this.editingLayoutId = null; // Track if editing existing layout
|
||||||
|
this.editingLayoutName = null;
|
||||||
|
this.selectorOverlay = null; // Layout selector UI
|
||||||
|
},
|
||||||
|
|
||||||
|
showLayoutSelector: function() {
|
||||||
|
if (this.isEditing || this.selectorOverlay) return;
|
||||||
|
|
||||||
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
|
// Create selector overlay
|
||||||
|
this.selectorOverlay = new St.Widget({
|
||||||
|
style_class: 'gridsnap-selector-overlay',
|
||||||
|
reactive: true,
|
||||||
|
x: monitor.x,
|
||||||
|
y: monitor.y,
|
||||||
|
width: monitor.width,
|
||||||
|
height: monitor.height
|
||||||
|
});
|
||||||
|
|
||||||
|
this.selectorOverlay.set_style(
|
||||||
|
'background-color: rgba(0, 0, 0, 0.7);'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create container for buttons
|
||||||
|
let buttonContainer = new St.BoxLayout({
|
||||||
|
vertical: true,
|
||||||
|
x: monitor.width / 2 - 200,
|
||||||
|
y: monitor.height / 2 - 200,
|
||||||
|
width: 400
|
||||||
|
});
|
||||||
|
buttonContainer.set_style(
|
||||||
|
'background-color: rgba(30, 30, 30, 0.95);' +
|
||||||
|
'padding: 20px;' +
|
||||||
|
'border-radius: 10px;' +
|
||||||
|
'spacing: 10px;'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add title
|
||||||
|
let title = new St.Label({
|
||||||
|
text: 'GridSnap Zone Editor'
|
||||||
|
});
|
||||||
|
title.set_style(
|
||||||
|
'color: white;' +
|
||||||
|
'font-size: 24px;' +
|
||||||
|
'font-weight: bold;' +
|
||||||
|
'margin-bottom: 10px;'
|
||||||
|
);
|
||||||
|
buttonContainer.add_child(title);
|
||||||
|
|
||||||
|
// Add subtitle
|
||||||
|
let subtitle = new St.Label({
|
||||||
|
text: 'Choose layout to edit or create new:'
|
||||||
|
});
|
||||||
|
subtitle.set_style(
|
||||||
|
'color: rgba(255, 255, 255, 0.8);' +
|
||||||
|
'font-size: 14px;' +
|
||||||
|
'margin-bottom: 20px;'
|
||||||
|
);
|
||||||
|
buttonContainer.add_child(subtitle);
|
||||||
|
|
||||||
|
// Add "Create New Layout" button
|
||||||
|
let newButton = this._createSelectorButton('+ Create New Layout', null, null);
|
||||||
|
buttonContainer.add_child(newButton);
|
||||||
|
|
||||||
|
// Add separator
|
||||||
|
let separator = new St.Widget({
|
||||||
|
height: 2
|
||||||
|
});
|
||||||
|
separator.set_style(
|
||||||
|
'background-color: rgba(255, 255, 255, 0.2);' +
|
||||||
|
'margin-top: 10px;' +
|
||||||
|
'margin-bottom: 10px;'
|
||||||
|
);
|
||||||
|
buttonContainer.add_child(separator);
|
||||||
|
|
||||||
|
// Add buttons for existing custom layouts
|
||||||
|
if (zoneManager && zoneManager.layouts) {
|
||||||
|
let hasCustomLayouts = false;
|
||||||
|
for (let layoutId in zoneManager.layouts) {
|
||||||
|
if (layoutId.startsWith('custom-')) {
|
||||||
|
hasCustomLayouts = true;
|
||||||
|
let layout = zoneManager.layouts[layoutId];
|
||||||
|
let button = this._createSelectorButton(
|
||||||
|
layout.name + ' (' + layout.zones.length + ' zones)',
|
||||||
|
layoutId,
|
||||||
|
layout
|
||||||
|
);
|
||||||
|
buttonContainer.add_child(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCustomLayouts) {
|
||||||
|
let noLayoutsLabel = new St.Label({
|
||||||
|
text: 'No custom layouts yet'
|
||||||
|
});
|
||||||
|
noLayoutsLabel.set_style(
|
||||||
|
'color: rgba(255, 255, 255, 0.5);' +
|
||||||
|
'font-size: 12px;' +
|
||||||
|
'font-style: italic;' +
|
||||||
|
'margin: 10px 0px;'
|
||||||
|
);
|
||||||
|
buttonContainer.add_child(noLayoutsLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cancel button
|
||||||
|
let cancelButton = new St.Button({
|
||||||
|
label: 'Cancel'
|
||||||
|
});
|
||||||
|
cancelButton.set_style(
|
||||||
|
'color: white;' +
|
||||||
|
'background-color: rgba(100, 100, 100, 0.5);' +
|
||||||
|
'padding: 10px 20px;' +
|
||||||
|
'border-radius: 5px;' +
|
||||||
|
'margin-top: 20px;'
|
||||||
|
);
|
||||||
|
cancelButton.connect('clicked', Lang.bind(this, function() {
|
||||||
|
this._closeLayoutSelector();
|
||||||
|
}));
|
||||||
|
buttonContainer.add_child(cancelButton);
|
||||||
|
|
||||||
|
this.selectorOverlay.add_child(buttonContainer);
|
||||||
|
|
||||||
|
// Add key press handler for ESC
|
||||||
|
this.selectorKeyPressId = this.selectorOverlay.connect('key-press-event',
|
||||||
|
Lang.bind(this, function(actor, event) {
|
||||||
|
let symbol = event.get_key_symbol();
|
||||||
|
if (symbol === Clutter.KEY_Escape) {
|
||||||
|
this._closeLayoutSelector();
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
}
|
||||||
|
return Clutter.EVENT_PROPAGATE;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
Main.layoutManager.addChrome(this.selectorOverlay);
|
||||||
|
Main.pushModal(this.selectorOverlay);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createSelectorButton: function(label, layoutId, layoutData) {
|
||||||
|
let button = new St.Button({
|
||||||
|
label: label
|
||||||
|
});
|
||||||
|
button.set_style(
|
||||||
|
'color: white;' +
|
||||||
|
'background-color: rgba(100, 149, 237, 0.5);' +
|
||||||
|
'padding: 15px 20px;' +
|
||||||
|
'border-radius: 5px;' +
|
||||||
|
'text-align: left;'
|
||||||
|
);
|
||||||
|
button.connect('clicked', Lang.bind(this, function() {
|
||||||
|
this._closeLayoutSelector();
|
||||||
|
this.startEditor(layoutId, layoutData);
|
||||||
|
}));
|
||||||
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
|
_closeLayoutSelector: function() {
|
||||||
|
if (!this.selectorOverlay) return;
|
||||||
|
|
||||||
|
if (this.selectorKeyPressId) {
|
||||||
|
this.selectorOverlay.disconnect(this.selectorKeyPressId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.popModal(this.selectorOverlay);
|
||||||
|
Main.layoutManager.removeChrome(this.selectorOverlay);
|
||||||
|
this.selectorOverlay.destroy();
|
||||||
|
this.selectorOverlay = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
startEditor: function() {
|
startEditor: function(layoutId, layoutData) {
|
||||||
if (this.isEditing) return;
|
if (this.isEditing) return;
|
||||||
|
|
||||||
this.isEditing = true;
|
this.isEditing = true;
|
||||||
this.zones = [];
|
|
||||||
|
// If editing existing layout, load it
|
||||||
|
if (layoutId && layoutData) {
|
||||||
|
this.editingLayoutId = layoutId;
|
||||||
|
this.editingLayoutName = layoutData.name;
|
||||||
|
this.zones = JSON.parse(JSON.stringify(layoutData.zones)); // Deep copy
|
||||||
|
} else {
|
||||||
|
this.editingLayoutId = null;
|
||||||
|
this.editingLayoutName = null;
|
||||||
|
this.zones = [];
|
||||||
|
}
|
||||||
|
|
||||||
let monitor = Main.layoutManager.primaryMonitor;
|
let monitor = Main.layoutManager.primaryMonitor;
|
||||||
|
|
||||||
// Create editor overlay
|
// Create editor overlay
|
||||||
@@ -116,10 +295,49 @@ ZoneEditor.prototype = {
|
|||||||
|
|
||||||
Main.layoutManager.addChrome(this.editorOverlay);
|
Main.layoutManager.addChrome(this.editorOverlay);
|
||||||
Main.pushModal(this.editorOverlay);
|
Main.pushModal(this.editorOverlay);
|
||||||
|
|
||||||
|
// If editing existing layout, create zone actors for existing zones
|
||||||
|
if (this.editingLayoutId && this.zones.length > 0) {
|
||||||
|
this._createZoneActorsFromData(monitor);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup key listener for save/cancel
|
// Setup key listener for save/cancel
|
||||||
this._setupEditorKeys();
|
this._setupEditorKeys();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_createZoneActorsFromData: function(monitor) {
|
||||||
|
// Create zone actors from existing zone data
|
||||||
|
this.zoneActors = [];
|
||||||
|
this.zones.forEach((zone, index) => {
|
||||||
|
let zoneActor = new St.Widget({
|
||||||
|
style_class: 'gridsnap-editor-zone',
|
||||||
|
x: zone.x * monitor.width,
|
||||||
|
y: zone.y * monitor.height,
|
||||||
|
width: zone.width * monitor.width,
|
||||||
|
height: zone.height * monitor.height
|
||||||
|
});
|
||||||
|
zoneActor.set_style(
|
||||||
|
'border: 3px solid rgba(100, 255, 100, 0.9);' +
|
||||||
|
'background-color: rgba(100, 255, 100, 0.3);' +
|
||||||
|
'border-radius: 4px;'
|
||||||
|
);
|
||||||
|
|
||||||
|
let label = new St.Label({
|
||||||
|
text: String(index + 1),
|
||||||
|
x: 10,
|
||||||
|
y: 10
|
||||||
|
});
|
||||||
|
label.set_style(
|
||||||
|
'color: white;' +
|
||||||
|
'font-size: 24px;' +
|
||||||
|
'font-weight: bold;' +
|
||||||
|
'text-shadow: 2px 2px 4px rgba(0,0,0,0.8);'
|
||||||
|
);
|
||||||
|
zoneActor.add_child(label);
|
||||||
|
this.editorOverlay.add_child(zoneActor);
|
||||||
|
this.zoneActors.push(zoneActor);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_setupEditorKeys: function() {
|
_setupEditorKeys: function() {
|
||||||
this.keyPressId = this.editorOverlay.connect('key-press-event',
|
this.keyPressId = this.editorOverlay.connect('key-press-event',
|
||||||
@@ -571,12 +789,21 @@ ZoneEditor.prototype = {
|
|||||||
this._cancelEditor();
|
this._cancelEditor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate layout name
|
let layoutId, layoutName;
|
||||||
let layoutId = 'custom-' + Date.now();
|
|
||||||
let layoutName = 'Custom Layout (' + this.zones.length + ' zones)';
|
// Check if editing existing layout or creating new
|
||||||
|
if (this.editingLayoutId) {
|
||||||
// Add to zoneManager's layouts
|
// Update existing layout
|
||||||
|
layoutId = this.editingLayoutId;
|
||||||
|
layoutName = this.editingLayoutName + ' (updated)';
|
||||||
|
} else {
|
||||||
|
// Create new layout
|
||||||
|
layoutId = 'custom-' + Date.now();
|
||||||
|
layoutName = 'Custom Layout (' + this.zones.length + ' zones)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to zoneManager's layouts
|
||||||
if (zoneManager) {
|
if (zoneManager) {
|
||||||
zoneManager.layouts[layoutId] = {
|
zoneManager.layouts[layoutId] = {
|
||||||
name: layoutName,
|
name: layoutName,
|
||||||
@@ -587,9 +814,13 @@ ZoneEditor.prototype = {
|
|||||||
// Persist to file for permanent storage
|
// Persist to file for permanent storage
|
||||||
zoneManager._saveLayouts();
|
zoneManager._saveLayouts();
|
||||||
|
|
||||||
Main.notify('GridSnap', 'Layout saved: ' + layoutName);
|
if (this.editingLayoutId) {
|
||||||
|
Main.notify('GridSnap', 'Layout updated: ' + layoutName);
|
||||||
|
} else {
|
||||||
|
Main.notify('GridSnap', 'Layout saved: ' + layoutName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._cancelEditor();
|
this._cancelEditor();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -678,7 +909,7 @@ ZoneManager.prototype = {
|
|||||||
Main.keybindingManager.addHotKey(
|
Main.keybindingManager.addHotKey(
|
||||||
'open-zone-editor',
|
'open-zone-editor',
|
||||||
'<Super><Shift>e',
|
'<Super><Shift>e',
|
||||||
Lang.bind(this, function() { this.editor.startEditor(); })
|
Lang.bind(this, function() { this.editor.showLayoutSelector(); })
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add keybindings for quick snap to zones (Super+Ctrl+1-9)
|
// Add keybindings for quick snap to zones (Super+Ctrl+1-9)
|
||||||
|
|||||||
Reference in New Issue
Block a user