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
|
||||
- [ ] May need to use `get_resource_scale()` or similar
|
||||
|
||||
### 5. Edit Existing Custom Layouts
|
||||
### 5. Edit Existing Custom Layouts ✅ COMPLETED
|
||||
**Priority: Medium**
|
||||
- [ ] Currently can only create new layouts in editor
|
||||
- [ ] Add ability to select and edit existing custom layouts
|
||||
- [ ] Add UI to choose which layout to edit
|
||||
- [ ] Pre-populate editor with existing zones when editing
|
||||
- [x] Currently can only create new layouts in editor
|
||||
- [x] Add ability to select and edit existing custom layouts
|
||||
- [x] Add UI to choose which layout to edit
|
||||
- [x] Pre-populate editor with existing zones when editing
|
||||
|
||||
### 15. Show Zone Dimensions in Editor ✅ COMPLETED
|
||||
**Priority: Medium**
|
||||
|
||||
259
extension.js
259
extension.js
@@ -66,14 +66,193 @@ ZoneEditor.prototype = {
|
||||
this.resizingZoneIndex = -1;
|
||||
this.resizeEdge = null; // 'n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'
|
||||
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;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// Create editor overlay
|
||||
@@ -116,10 +295,49 @@ ZoneEditor.prototype = {
|
||||
|
||||
Main.layoutManager.addChrome(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
|
||||
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() {
|
||||
this.keyPressId = this.editorOverlay.connect('key-press-event',
|
||||
@@ -571,12 +789,21 @@ ZoneEditor.prototype = {
|
||||
this._cancelEditor();
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate layout name
|
||||
let layoutId = 'custom-' + Date.now();
|
||||
let layoutName = 'Custom Layout (' + this.zones.length + ' zones)';
|
||||
|
||||
// Add to zoneManager's layouts
|
||||
|
||||
let layoutId, layoutName;
|
||||
|
||||
// Check if editing existing layout or creating new
|
||||
if (this.editingLayoutId) {
|
||||
// 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) {
|
||||
zoneManager.layouts[layoutId] = {
|
||||
name: layoutName,
|
||||
@@ -587,9 +814,13 @@ ZoneEditor.prototype = {
|
||||
// Persist to file for permanent storage
|
||||
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();
|
||||
},
|
||||
|
||||
@@ -678,7 +909,7 @@ ZoneManager.prototype = {
|
||||
Main.keybindingManager.addHotKey(
|
||||
'open-zone-editor',
|
||||
'<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)
|
||||
|
||||
Reference in New Issue
Block a user