Project: add "Open in new window" button to project dropdown + warn when leaving dirty project

Goal

Extend the project dropdown in the main window top bar with two improvements:

  1. Open in new window: Add a small button next to each project entry that opens the selected project in a separate window.
  2. Dirty asset warning on project switch: When switching away from a project that has dirty (modified) assets, warn the user before leaving. We already do this when closing the window — create a small internal API so multiple parts of the program can reuse the same dirty-check + confirmation logic.

Context / integration points

  • Project dropdown in the main window top bar (Studio shell).
  • Dirty asset tracking already exists in the asset system.
  • Window-close dirty check already exists — extract into a shared API module.
  • The "open in new window" button should be small and unobtrusive, similar to IDE patterns.

Design

  • Small icon button (e.g. ↗️) at the right edge of each project entry in the dropdown.
  • Clicking opens a new OS window with the selected project loaded.
  • On project switch, if the current project has dirty assets, show a confirmation dialog before switching.
  • Extract the dirty-check logic from window-close into editor/dirty_check.zig or similar shared module.
  • The internal API should be simple: checkDirtyAndConfirm(project) -> bool / callback-based.

Tasks

  • Add small "open in new window" button to project dropdown entries
  • Implement opening a project in a new OS window
  • Extract dirty-check + confirmation from window-close handler into shared API
  • Wire the shared API into project-switch flow
  • Test with dirty assets (modified texture, scene, etc.)
  • Test with clean project (no prompt)

Acceptance criteria

  • Each project in the dropdown has a small button to open it in a new window.
  • Switching away from a project with dirty assets shows a confirmation dialog.
  • Switching away from a clean project happens immediately without prompt.
  • The window-close path continues to use the same shared API and works identically.