Skip to content
Merged
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
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
Versioned entries use `MAJOR.MINOR.BUILD` from [Version.h](CassoCore/Version.h).
Entries before versioning was introduced use dates only.

## [1.5.1398] — Themed disk-insert MRU picker

The runtime disk-insert flow (Disk → Insert Disk Image, drive-widget
click, or `IDM_DISK_INSERT1`/`2`) now opens the themed MRU picker
instead of jumping straight to `IFileOpenDialog`. Lists the same
existing-on-disk recents as the boot picker plus the always-available
DOS 3.3 / ProDOS download rows. A **Browse...** button preserves the
native `IFileOpenDialog` path for off-MRU images.

### Added
- **feat(011): `AssetBootstrap::PromptInsertDiskMru`.** Sibling to the
boot-time `PromptBootDiskMru` — same `ListView`-based DialogPrimitive
surface, theme-aware, but titled per drive and wired with a Browse
fallback instead of Skip. Uses out-of-range negative sentinel result
codes to avoid colliding with row indices.
- **feat(011): `WindowCommandManager::PromptInsertDiskMru`.** Loads MRU
from `GlobalUserPrefs::recentDisks`, prunes vanished files, invokes
the themed picker, and routes the result: chosen row →
`EmulatorShell::Mount(6, drive, path)`; Browse → existing
`PromptForDiskImage` (`IFileOpenDialog`); Cancel/Esc/close → no-op.

### Changed
- **refactor(011): `OnDiskCommand` IDM_DISK_INSERT1/2** now invoke
`PromptInsertDiskMru` instead of `PromptForDiskImage` directly.

## [1.5.1395] — Native dialogs migration (spec 011)

Themed DX-based modal dialogs now replace every Win32 `MessageBoxW` /
Expand Down
181 changes: 181 additions & 0 deletions Casso/AssetBootstrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,187 @@ HRESULT AssetBootstrap::PromptBootDiskMru (



////////////////////////////////////////////////////////////////////////////////
//
// PromptInsertDiskMru
//
// Runtime-insert sibling of PromptBootDiskMru. Same MRU + DOS 3.3 /
// ProDOS "Download" rows, but the dialog footer offers Browse... /
// Cancel instead of Skip. Browse pops back to the caller which then
// fires the existing IFileOpenDialog path. Cancel / close box leaves
// the drive untouched.
//
////////////////////////////////////////////////////////////////////////////////

HRESULT AssetBootstrap::PromptInsertDiskMru (
HINSTANCE hInstance,
HWND hwndParent,
int drive,
const vector<fs::path> & mruEntries,
const fs::path & diskDir,
std::string_view themeName,
wstring & outDiskPath,
bool & outBrowse,
string & outError)
{
struct DownloadRow { const BootDiskSpec * spec; wstring label; };

static constexpr int s_kBrowseResult = -2000;
static constexpr int s_kCancelResult = -2001;
static constexpr int s_kCloseBoxResult = -1000;

HRESULT hr = S_OK;
DialogDefinition def = {};
wstring title;
wstring intro;
DownloadRow downloads[] =
{
{ &s_kDos33Disk, L"DOS 3.3" },
{ &s_kProDOSDisk, L"ProDOS" }
};
int chosen = IDCANCEL;
int mruCount = (int) mruEntries.size();
int downloadCount = (int) std::size (downloads);
int rowCount = mruCount + downloadCount;
UINT sysDpi = (hwndParent != nullptr) ? GetDpiForWindow (hwndParent)
: GetDpiForSystem();
ListView list;
error_code ec;


outDiskPath.clear();
outBrowse = false;

title = L"Casso ";
title += s_kchEmDash;
title += format (L" Insert Disk in Drive {}", drive);

if (mruCount > 0)
{
intro = format (L"Choose a disk image for Drive {}, browse for "
L"another, or download a stock master from the "
L"Asimov archive.", drive);
}
else
{
intro = format (L"No recent disks for Drive {}. Browse for an "
L"image, or download a stock master from the "
L"Asimov archive.", drive);
}

{
std::vector<ListView::Column> cols;
std::vector<std::vector<ListView::Cell>> rows;

cols.push_back ({ L"Disk image", 0, false, DwriteTextRenderer::HAlign::Left });
cols.push_back ({ L"Location", 0, false, DwriteTextRenderer::HAlign::Left });

rows.reserve ((size_t) rowCount);

for (const auto & p : mruEntries)
{
ListView::Cell name { p.filename().wstring(), false };
ListView::Cell loc { p.parent_path().wstring(), true };
rows.push_back ({ std::move (name), std::move (loc) });
}

for (const DownloadRow & dr : downloads)
{
fs::path wantPath = diskDir / string (dr.spec->cassoName);
bool present = fs::exists (wantPath, ec);
ListView::Cell name { dr.label, false };
ListView::Cell loc { present ? wantPath.parent_path().wstring()
: L"Asimov archive (Download)",
true };
rows.push_back ({ std::move (name), std::move (loc) });
}

list.SetDpi (sysDpi);
list.SetShowHeader (true);
list.SetColumns (std::move (cols));
list.SetRows (std::move (rows));
}

def.title = title;
def.icon = DialogIcon::AppFlat;
def.iconSizeOverrideDp = 64.0f;
def.body.push_back ({ intro, false, L"" });
def.customBodyMinSizePx.cx = MulDiv (s_kBootMruBodyWidthDp, (int) sysDpi, 96);
def.customBodyMinSizePx.cy = list.RequiredHeightPx();

def.onMeasureCustomBody = [&list, sysDpi] (DwriteTextRenderer & text, float /*dpiScale*/) -> SIZE
{
list.SetDpi (sysDpi);
list.MeasureColumnsPx (text);
SIZE sz {};
sz.cx = list.TotalMeasuredWidthPx();
sz.cy = list.RequiredHeightPx();
return sz;
};

def.onPaintCustomBody = [&list] (DialogPaintContext & ctx)
{
if (ctx.painter == nullptr || ctx.text == nullptr)
{
return;
}

list.SetTheme (ctx.theme);
list.SetRect (ctx.customBodyRect);
list.Paint (*ctx.painter, *ctx.text);
};

def.onInputCustomBody = [&list] (const DialogInputEvent & ev) -> std::optional<int>
{
int idx = list.HitTestRow (ev.xPx, ev.yPx);

if (ev.kind == DialogInputEvent::Kind::MouseMove)
{
list.SetHoveredRow (idx);
return std::nullopt;
}

if (ev.kind == DialogInputEvent::Kind::LeftButtonUp && idx >= 0)
{
return idx;
}

return std::nullopt;
};

def.buttons.push_back ({ L"&Browse...", s_kBrowseResult, false, false });
def.buttons.push_back ({ L"Cancel", s_kCancelResult, true, true });
def.closeBoxResult = s_kCloseBoxResult;

chosen = ShowStandaloneDialog (hInstance, hwndParent, themeName, def);

if (chosen == s_kBrowseResult)
{
outBrowse = true;
}
else if (chosen == s_kCloseBoxResult || chosen == s_kCancelResult)
{
// user cancelled; outDiskPath stays empty
}
else if (chosen >= 0 && chosen < mruCount)
{
outDiskPath = mruEntries[(size_t) chosen].wstring();
}
else if (chosen >= mruCount && chosen < rowCount)
{
const BootDiskSpec & spec = *downloads[chosen - mruCount].spec;
hr = DownloadStockBootDisk (spec, diskDir, outDiskPath, outError);
CHR (hr);
}

Error:
return hr;
}





////////////////////////////////////////////////////////////////////////////////
//
// FetchAndDecodeOgg
Expand Down
24 changes: 24 additions & 0 deletions Casso/AssetBootstrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,30 @@ class AssetBootstrap
bool & outUserClosed,
string & outError);

// Themed runtime disk-insert picker. Mirrors PromptBootDiskMru
// but is used when the user clicks a drive widget (or invokes
// Disk -> Insert in drive N) on a running machine. Lists the
// user's recent disk images plus "Download" rows for the DOS 3.3
// and ProDOS stock masters so the picker is never empty even on
// a fresh install. The "Browse..." footer button falls through to
// the Win32 IFileOpenDialog for ad-hoc images. Cancel / close box
// leaves the slot untouched.
//
// On return:
// outDiskPath = path to mount, or empty if the user cancelled
// or chose Browse (caller then runs IFileOpenDialog)
// outBrowse = true if the user clicked Browse... (caller
// should fall through to its file-picker path)
static HRESULT PromptInsertDiskMru (HINSTANCE hInstance,
HWND hwndParent,
int drive,
const vector<fs::path> & mruEntries,
const fs::path & diskDir,
std::string_view themeName,
wstring & outDiskPath,
bool & outBrowse,
string & outError);

// Unified startup downloader. Inspects the current install for
// every required-or-optional asset that's missing (ROMs from the
// catalog, Disk II drive audio per mechanism) and presents a
Expand Down
2 changes: 1 addition & 1 deletion Casso/EmulatorShell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1592,7 +1592,7 @@ void EmulatorShell::BrowseForDisk (int drive)
Sleep (8);
}

hrBrowse = m_windowCommandManager->PromptForDiskImage (drive);
hrBrowse = m_windowCommandManager->PromptInsertDiskMru (drive);
IGNORE_RETURN_VALUE (hrBrowse, S_OK);

// Cancel / error path: door follows mount state. Mounted drive
Expand Down
67 changes: 62 additions & 5 deletions Casso/Shell/WindowCommandManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include "WindowCommandManager.h"

#include "../AssetBootstrap.h"
#include "../EmulatorShell.h"
#include "../resource.h"
#include "../Shell/DiskMru.h"
#include "Version.h"
#include "Ui/Chrome/LayoutManager.h"
#include "Ui/Chrome/ChromeMetrics.h"
Expand Down Expand Up @@ -425,6 +427,65 @@ HRESULT WindowCommandManager::PromptForDiskImage (int drive)



////////////////////////////////////////////////////////////////////////////////
//
// PromptInsertDiskMru
//
// Shows the themed disk MRU picker. Routes the user's chosen disk
// (recent image or stock master download) to Mount(); if the user
// clicks "Browse..." this falls through to the IFileOpenDialog path
// via PromptForDiskImage.
//
////////////////////////////////////////////////////////////////////////////////

HRESULT WindowCommandManager::PromptInsertDiskMru (int drive)
{
HRESULT hr = S_OK;
DiskMru mru;
std::vector<std::filesystem::path> mruExisting;
std::filesystem::path diskDir;
std::wstring chosenPath;
std::string error;
bool userBrowsed = false;


diskDir = AssetBootstrap::GetDiskDirectory();
mru = DiskMru::FromUtf8 (m_shell.m_globalPrefs.recentDisks);
mruExisting = mru.Prune ([] (const std::filesystem::path & p)
{
return std::filesystem::exists (p);
});

hr = AssetBootstrap::PromptInsertDiskMru (GetModuleHandle (nullptr),
m_shell.m_hwnd,
drive,
mruExisting,
diskDir,
m_shell.m_globalPrefs.activeTheme,
chosenPath,
userBrowsed,
error);
CHR (hr);

if (userBrowsed)
{
hr = PromptForDiskImage (drive);
CHR (hr);
}
else if (!chosenPath.empty())
{
hr = m_shell.Mount (6, drive, chosenPath);
CHR (hr);
}

Error:
return hr;
}





////////////////////////////////////////////////////////////////////////////////
//
// OnDiskCommand
Expand All @@ -443,13 +504,9 @@ void WindowCommandManager::OnDiskCommand (int id)
case IDM_DISK_INSERT1:
case IDM_DISK_INSERT2:
{
// Route both insert commands through the modern
// IFileOpenDialog-based picker. FR-015 keeps
// IFileOpenDialog as the supported file-picker surface;
// the legacy GetOpenFileNameW path is removed.
drive = (id == IDM_DISK_INSERT1) ? 1 : 2;

hr = PromptForDiskImage (drive);
hr = PromptInsertDiskMru (drive);
IGNORE_RETURN_VALUE (hr, S_OK);
break;
}
Expand Down
3 changes: 2 additions & 1 deletion Casso/Shell/WindowCommandManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class WindowCommandManager

bool OnInitMenuPopup (HWND hwnd, HMENU hMenu, UINT itemIndex, bool isWindowMenu);

HRESULT PromptForDiskImage (int drive);
HRESULT PromptForDiskImage (int drive);
HRESULT PromptInsertDiskMru (int drive);

private:
EmulatorShell & m_shell;
Expand Down
Loading
Loading