diff --git a/desktop/src/app/components/taskbar/taskbar.component.html b/desktop/src/app/components/taskbar/taskbar.component.html new file mode 100644 index 0000000..4909941 --- /dev/null +++ b/desktop/src/app/components/taskbar/taskbar.component.html @@ -0,0 +1,54 @@ +
+ + + + +
+ + + + +
+ + +
+ + +
+ @for (win of windows(); track win.id) { + + } +
+ + +
+ + {{ time() }} + {{ date() }} + +
+
diff --git a/desktop/src/app/components/taskbar/taskbar.component.scss b/desktop/src/app/components/taskbar/taskbar.component.scss new file mode 100644 index 0000000..944a3de --- /dev/null +++ b/desktop/src/app/components/taskbar/taskbar.component.scss @@ -0,0 +1,196 @@ +:host { + display: block; + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 9000; +} + +.taskbar { + display: flex; + align-items: center; + height: 48px; + padding: 0 4px; + background: linear-gradient(180deg, #2b2b2b 0%, #1e1e1e 100%); + border-top: 1px solid #404040; + backdrop-filter: blur(20px); +} + +// ── Start button ────────────────────────────────────────────────────────── + +.start-btn { + width: 48px; + height: 40px; + border: none; + background: transparent; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background 0.15s; + + &:hover { + background: rgba(255, 255, 255, 0.08); + } + + &:active { + background: rgba(255, 255, 255, 0.04); + } +} + +// ── Quick launch ────────────────────────────────────────────────────────── + +.quick-launch { + display: flex; + align-items: center; + gap: 2px; + margin-left: 4px; +} + +.launch-btn { + width: 40px; + height: 40px; + border: none; + background: transparent; + font-size: 16px; + cursor: pointer; + border-radius: 4px; + transition: background 0.15s; + + &:hover { + background: rgba(255, 255, 255, 0.08); + } +} + +// ── Separator ───────────────────────────────────────────────────────────── + +.taskbar-separator { + width: 1px; + height: 24px; + background: #555; + margin: 0 6px; +} + +// ── Window list ─────────────────────────────────────────────────────────── + +.window-list { + flex: 1; + display: flex; + align-items: center; + gap: 2px; + overflow-x: auto; + padding: 0 4px; + + &::-webkit-scrollbar { + display: none; + } +} + +.taskbar-item { + display: flex; + align-items: center; + gap: 6px; + height: 38px; + padding: 0 10px; + border: none; + background: rgba(255, 255, 255, 0.04); + color: #ccc; + font-size: 12px; + cursor: pointer; + border-radius: 4px; + white-space: nowrap; + max-width: 200px; + min-width: 0; + transition: background 0.15s; + position: relative; + + &::after { + content: ''; + position: absolute; + bottom: 2px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: #0078d4; + border-radius: 1px; + transition: width 0.2s; + } + + &.active::after { + width: 70%; + } + + &.minimized { + opacity: 0.6; + + &::after { + width: 40%; + background: #888; + } + } + + &:hover { + background: rgba(255, 255, 255, 0.08); + } +} + +.item-icon { + font-size: 14px; + flex-shrink: 0; +} + +.item-title { + overflow: hidden; + text-overflow: ellipsis; +} + +.item-close { + display: none; + font-size: 10px; + width: 18px; + height: 18px; + line-height: 18px; + text-align: center; + border-radius: 3px; + color: #aaa; + margin-left: auto; + + &:hover { + background: #e81123; + color: #fff; + } +} + +.taskbar-item:hover .item-close { + display: block; +} + +// ── System tray ─────────────────────────────────────────────────────────── + +.system-tray { + display: flex; + align-items: center; + padding: 0 8px; + height: 100%; +} + +.tray-clock { + display: flex; + flex-direction: column; + align-items: flex-end; + color: #ccc; + font-size: 12px; + line-height: 1.3; +} + +.clock-time { + font-weight: 500; +} + +.clock-date { + font-size: 11px; + opacity: 0.7; +} diff --git a/desktop/src/app/components/taskbar/taskbar.component.ts b/desktop/src/app/components/taskbar/taskbar.component.ts new file mode 100644 index 0000000..348e7dd --- /dev/null +++ b/desktop/src/app/components/taskbar/taskbar.component.ts @@ -0,0 +1,51 @@ +import { Component, computed, output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { WindowManagerService, type WindowState } from '../../services/window-manager.service'; + +@Component({ + selector: 'app-taskbar', + standalone: true, + imports: [CommonModule], + templateUrl: './taskbar.component.html', + styleUrl: './taskbar.component.scss', +}) +export class TaskbarComponent { + /** Emitted when the start button is clicked. */ + readonly startClick = output(); + + /** Emitted when a taskbar item requests to launch an app. */ + readonly launchApp = output(); + + /** Current time string. */ + readonly time = computed(() => { + const now = new Date(); + return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + }); + + /** Current date string. */ + readonly date = computed(() => { + const now = new Date(); + return now.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' }); + }); + + constructor(public wm: WindowManagerService) {} + + /** All windows for the taskbar items. */ + readonly windows = computed(() => this.wm.allWindows()); + + onFocusWindow(id: string): void { + const win = this.wm.getWindow(id); + if (win?.isMinimized) { + this.wm.focusWindow(id); + } else if (win?.isActive) { + this.wm.minimizeWindow(id); + } else { + this.wm.focusWindow(id); + } + } + + onCloseWindow(id: string, event: MouseEvent): void { + event.stopPropagation(); + this.wm.closeWindow(id); + } +}