Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/app/PickleGlassApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
import { SettingsView } from '../features/settings/SettingsView.js';
import { AssistantView } from '../features/listen/AssistantView.js';
import { AskView } from '../features/ask/AskView.js';
import { TaskView } from '../features/task/TaskView.js';

import '../features/listen/renderer/renderer.js';

Expand All @@ -22,7 +23,7 @@ export class PickleGlassApp extends LitElement {
height: 100%;
}

ask-view, settings-view, history-view, help-view, setup-view {
ask-view, settings-view, history-view, help-view, setup-view, task-view {
display: block;
width: 100%;
height: 100%;
Expand Down Expand Up @@ -274,6 +275,8 @@ export class PickleGlassApp extends LitElement {
return html`<help-view></help-view>`;
case 'setup':
return html`<setup-view></setup-view>`;
case 'task':
return html`<task-view></task-view>`;
default:
return html`<div>Unknown view: ${this.currentView}</div>`;
}
Expand Down
439 changes: 54 additions & 385 deletions src/common/prompts/promptTemplates.js

Large diffs are not rendered by default.

105 changes: 75 additions & 30 deletions src/electron/windowLayoutManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class WindowLayoutManager {
const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY);

this.positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
this.positionTaskWindow(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
this.positionSettingsWindow(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
}

Expand Down Expand Up @@ -100,44 +101,88 @@ class WindowLayoutManager {
if (!askVisible && !listenVisible) return;

const PAD = 8;
const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
let askBounds = askVisible ? ask.getBounds() : null;
let listenBounds = listenVisible ? listen.getBounds() : null;

if (askVisible && listenVisible) {
const combinedWidth = listenBounds.width + PAD + askBounds.width;
let groupStartXRel = headerCenterXRel - combinedWidth / 2;
let listenXRel = groupStartXRel;
let askXRel = groupStartXRel + listenBounds.width + PAD;

if (listenXRel < PAD) {
listenXRel = PAD;
askXRel = listenXRel + listenBounds.width + PAD;
}
if (askXRel + askBounds.width > screenWidth - PAD) {
askXRel = screenWidth - PAD - askBounds.width;
listenXRel = askXRel - listenBounds.width - PAD;
}
// Position ask window on the left side as a vertical bar (only if not moved by user)
if (askVisible && !ask.__userMoved) {
const askX = workAreaX + PAD; // Left edge of screen with padding
const askY = workAreaY + PAD; // Top with padding
const askWidth = askBounds.width;
const askHeight = Math.min(askBounds.height, screenHeight - 2 * PAD); // Fit within screen height

ask.setBounds({
x: askX,
y: askY,
width: askWidth,
height: askHeight
});
}

let yRel = (strategy.primary === 'above')
? headerBounds.y - workAreaY - Math.max(askBounds.height, listenBounds.height) - PAD
: headerBounds.y - workAreaY + headerBounds.height + PAD;
// Position listen window normally (relative to header) if ask is not visible,
// or to the right of ask window if both are visible
if (listenVisible) {
if (askVisible && !ask.__userMoved) {
// Position listen to the right of ask window (only if ask is in default position)
const listenX = workAreaX + PAD + askBounds.width + PAD;
const listenY = workAreaY + PAD;
const listenWidth = listenBounds.width;
const listenHeight = Math.min(listenBounds.height, screenHeight - 2 * PAD);

listen.setBounds({
x: listenX,
y: listenY,
width: listenWidth,
height: listenHeight
});
} else {
// Position listen normally relative to header when ask is not visible
const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
let xRel = headerCenterXRel - listenBounds.width / 2;
let yRel = (strategy.primary === 'above')
? headerBounds.y - workAreaY - listenBounds.height - PAD
: headerBounds.y - workAreaY + headerBounds.height + PAD;

listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(yRel + workAreaY), width: listenBounds.width, height: listenBounds.height });
ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(yRel + workAreaY), width: askBounds.width, height: askBounds.height });
} else {
const win = askVisible ? ask : listen;
const winBounds = askVisible ? askBounds : listenBounds;
let xRel = headerCenterXRel - winBounds.width / 2;
let yRel = (strategy.primary === 'above')
? headerBounds.y - workAreaY - winBounds.height - PAD
: headerBounds.y - workAreaY + headerBounds.height + PAD;
xRel = Math.max(PAD, Math.min(screenWidth - listenBounds.width - PAD, xRel));
yRel = Math.max(PAD, Math.min(screenHeight - listenBounds.height - PAD, yRel));

xRel = Math.max(PAD, Math.min(screenWidth - winBounds.width - PAD, xRel));
yRel = Math.max(PAD, Math.min(screenHeight - winBounds.height - PAD, yRel));
listen.setBounds({ x: Math.round(xRel + workAreaX), y: Math.round(yRel + workAreaY), width: listenBounds.width, height: listenBounds.height });
}
}
}

win.setBounds({ x: Math.round(xRel + workAreaX), y: Math.round(yRel + workAreaY), width: winBounds.width, height: winBounds.height });
/**
* 'task' 창의 위치를 조정합니다 (항상 상단 우측).
*/
positionTaskWindow(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY) {
const task = this.windowPool.get('task');
console.log('[WindowLayoutManager] positionTaskWindow called');
console.log('[WindowLayoutManager] task window exists:', !!task);
console.log('[WindowLayoutManager] task window visible:', task ? task.isVisible() : 'N/A');

if (!task?.getBounds || !task.isVisible()) {
console.log('[WindowLayoutManager] Task window not available or not visible');
return;
}

const taskBounds = task.getBounds();
const PAD = 16;

// Position in top right corner of screen
const taskX = workAreaX + screenWidth - taskBounds.width - PAD;
const taskY = workAreaY + PAD;

console.log('[WindowLayoutManager] Positioning task window at:', { taskX, taskY, width: taskBounds.width, height: taskBounds.height });
console.log('[WindowLayoutManager] Screen dimensions:', { screenWidth, screenHeight, workAreaX, workAreaY });

task.setBounds({
x: taskX,
y: taskY,
width: taskBounds.width,
height: taskBounds.height
});

console.log('[WindowLayoutManager] Task window positioned successfully');
}

/**
Expand Down
83 changes: 79 additions & 4 deletions src/electron/windowManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if (shouldUseLiquidGlass) {
}
/* ────────────────[ GLASS BYPASS ]─────────────── */

let isContentProtectionOn = true;
let isContentProtectionOn = false;
let currentDisplayId = null;

let mouseEventsIgnored = false;
Expand Down Expand Up @@ -62,14 +62,18 @@ let movementManager = null;

let storedProvider = 'openai';

const featureWindows = ['listen','ask','settings'];
const featureWindows = ['listen','ask','settings','task'];
function isAllowed(name) {
if (name === 'header') return true;
return featureWindows.includes(name) && currentHeaderState === 'main';
}

function createFeatureWindows(header) {
if (windowPool.has('listen')) return;
console.log('[WindowManager] createFeatureWindows called');
if (windowPool.has('listen')) {
console.log('[WindowManager] Feature windows already exist');
return;
}

const commonChildOptions = {
parent: header,
Expand Down Expand Up @@ -119,7 +123,18 @@ function createFeatureWindows(header) {
windowPool.set('listen', listen);

// ask
const ask = new BrowserWindow({ ...commonChildOptions, width:600 });
const ask = new BrowserWindow({
...commonChildOptions,
parent: undefined, // Remove parent to allow independent dragging
width: 320, // Narrower for vertical bar
height: 800, // Increased from 600 to 800 for better vertical space
minWidth: 280, // Minimum width constraint
maxWidth: 400, // Maximum width constraint
minHeight: 500, // Increased minimum height
maxHeight: 1000, // Increased from 800 to 1000 for maximum height
movable: true, // Allow dragging
resizable: true // Allow resizing
});
ask.setContentProtection(isContentProtectionOn);
ask.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true});
if (process.platform === 'darwin') {
Expand Down Expand Up @@ -148,6 +163,12 @@ function createFeatureWindows(header) {

ask.on('blur',()=>ask.webContents.send('window-blur'));

// Track if user has manually moved the ask window
ask.on('moved', () => {
ask.__userMoved = true;
console.log('[WindowManager] Ask window moved by user - disabling auto-positioning');
});

// Open DevTools in development
if (!app.isPackaged) {
ask.webContents.openDevTools({ mode: 'detach' });
Expand Down Expand Up @@ -184,6 +205,44 @@ function createFeatureWindows(header) {
});
}
windowPool.set('settings', settings);

// task
const task = new BrowserWindow({
...commonChildOptions,
width: 320,
height: 300,
minWidth: 280,
maxWidth: 400,
minHeight: 200,
maxHeight: 500,
parent: undefined
});
task.setContentProtection(isContentProtectionOn);
task.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true});
if (process.platform === 'darwin') {
task.setWindowButtonVisibility(false);
}
const taskLoadOptions = { query: { view: 'task' } };
if (!shouldUseLiquidGlass) {
task.loadFile(path.join(__dirname, '../app/content.html'), taskLoadOptions);
}
else {
taskLoadOptions.query.glass = 'true';
task.loadFile(path.join(__dirname, '../app/content.html'), taskLoadOptions);
task.webContents.once('did-finish-load', () => {
const viewId = liquidGlass.addView(task.getNativeWindowHandle(), {
cornerRadius: 12,
tintColor: '#FF00001A', // Red tint
opaque: false,
});
if (viewId !== -1) {
liquidGlass.unstable_setVariant(viewId, 2);
}
});
}
windowPool.set('task', task);
console.log('[WindowManager] Task window created and added to pool');
console.log('[WindowManager] Current windowPool keys:', Array.from(windowPool.keys()));
}

function destroyFeatureWindows() {
Expand Down Expand Up @@ -759,6 +818,18 @@ function setupIpcHandlers(movementManager) {
return isContentProtectionOn;
});

// Reset ask window position
ipcMain.handle('reset-ask-window-position', () => {
const ask = windowPool.get('ask');
if (ask && !ask.isDestroyed()) {
ask.__userMoved = false;
console.log('[WindowManager] Ask window position reset - re-enabling auto-positioning');
updateLayout(); // Trigger layout update to reposition
return { success: true };
}
return { success: false, error: 'Ask window not found' };
});

ipcMain.on('header-state-changed', (event, state) => {
console.log(`[WindowManager] Header state changed to: ${state}`);
currentHeaderState = state;
Expand Down Expand Up @@ -1109,6 +1180,10 @@ function setupIpcHandlers(movementManager) {
askWindow.hide();
}
});

ipcMain.on('update-layout', () => {
updateLayout();
});
}


Expand Down
Loading