--- nono/wx/wxvideomonitor.cpp 2026/04/29 17:04:28 1.1 +++ nono/wx/wxvideomonitor.cpp 2026/04/29 17:05:08 1.1.1.6 @@ -1,11 +1,35 @@ // // nono -// Copyright (C) 2019 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt +// + +// +// ビデオ系モニターウィンドウ // -#include "wxheader.h" #include "wxvideomonitor.h" -#include "bitmap.h" +#include "wxuimessage.h" +#include "bt454.h" +#include "lunafb.h" +#include "renderer.h" + +enum { + ID_PLANE0 = IDGROUP_VIDEO, + ID_PLANE1, + ID_PLANE2, + ID_PLANE3, + ID_PLANE4, + ID_PLANE5, + ID_PLANE6, + ID_PLANE7, + ID_PALETTE, + + ID_local_end, // 最後に置く (チェック用) +}; +static_assert(ID_local_end <= (int)IDGROUP_VIDEO_END, "ID exceeded"); + +#define ID_PLANE(id) (ID_PLANE0 + (id)) // // LUNA ビットマップパネル @@ -13,33 +37,72 @@ // イベントテーブル wxBEGIN_EVENT_TABLE(WXLunaBitmapPanel, inherited) - EVT_PAINT(WXLunaBitmapPanel::OnPaint) EVT_SIZE(WXLunaBitmapPanel::OnSize) - EVT_SCROLLWIN(WXLunaBitmapPanel::OnScrollWin) + EVT_TIMER(wxID_ANY, WXLunaBitmapPanel::OnTimer) wxEND_EVENT_TABLE() // コンストラクタ WXLunaBitmapPanel::WXLunaBitmapPanel(wxWindow *parent) - : inherited(parent, wxID_ANY, wxDefaultPosition, - wxSize(512, 384)) // 初期値は適当 + : inherited(parent, wxDefaultSize) { - src = gBitmap->GetBuf(); + // スクロールバーを自分で持つ (配置は Layout() で行う) + vscroll = new WXScrollBar(this, wxID_ANY, wxSB_VERTICAL); + hscroll = new WXScrollBar(this, wxID_ANY, wxSB_HORIZONTAL); + + // Sizer 使わず自前でレイアウトする。 + SetAutoLayout(true); + + // 最小値は適当 + int vscr_width = vscroll->GetClientSize().x; + int hscr_height = hscroll->GetClientSize().y; + SetMinClientSize(wxSize(16 + vscr_width, 16 + hscr_height)); + SetMaxClientSize(wxSize(virtual_width + vscr_width, + virtual_height + hscr_height)); + + // スクロールバーからの通知イベントを受け取る + vscroll->Connect(NONO_EVT_SCROLL, + wxScrollEventHandler(WXLunaBitmapPanel::OnScroll), NULL, this); + hscroll->Connect(NONO_EVT_SCROLL, + wxScrollEventHandler(WXLunaBitmapPanel::OnScroll), NULL, this); + + // VM からの通知を受け取る + WXUIMessage::Connect(UIMessage::PALETTE, this, + wxCommandEventHandler(WXLunaBitmapPanel::OnPaletteChanged)); + + renderer = dynamic_cast(gRenderer); - // 仮想キャンバスサイズを設定 - SetVirtualSize(virtual_width, virtual_height); - // スクロールバーを設定 - SetScrollRate(width_per_unit, height_per_unit); - SetScrollbars(width_per_unit, height_per_unit, - virtual_width / width_per_unit, - virtual_height / height_per_unit); + // 更新タイマーはモニターパネル同様の 30Hz にしておく。 + timer.SetOwner(this); + timer.Start(33); } // デストラクタ WXLunaBitmapPanel::~WXLunaBitmapPanel() { - if (bmpbuf) { - delete bmpbuf; + WXUIMessage::Disconnect(UIMessage::PALETTE, + wxCommandEventHandler(WXLunaBitmapPanel::OnPaletteChanged)); +} + +bool +WXLunaBitmapPanel::Layout() +{ + if (inherited::Layout() == false) { + return false; } + + // クライアント領域内にスクロールバーを置く + // (その分のスペースは加味してある) + const wxSize size = GetClientSize(); + int vscr_width = vscroll->GetClientSize().x; + int hscr_height = hscroll->GetClientSize().y; + int view_width = size.x - vscr_width; + int view_height = size.y - hscr_height; + vscroll->SetSize(view_width, 0, vscr_width, view_height); + hscroll->SetSize(0, view_height, view_width, hscr_height); + + // 右下の空き地は誰も描画がはみ出さなければ最初の背景色のままのはず + + return true; } // サイズ変更イベント @@ -53,110 +116,230 @@ WXLunaBitmapPanel::OnSize(wxSizeEvent& e return; } - view_width = size.x; - view_height = size.y; + inherited::OnSize(event); - // BMP バッファを持っていれば一旦解放 - if (bmpbuf) { - delete bmpbuf; - bmpbuf = NULL; + // スクロールバー分を除いたところが描画領域 + size.x -= vscroll->GetClientSize().x; + size.y -= hscroll->GetClientSize().y; + + // スクロールバーが右端まで行っている場合は右を固定したように見せる + // ただし全体が見えている場合は除く。 + + if (view.x > 0 && + (view.x + view.w == virtual_width || + view.x + size.x >= virtual_width)) + { + view.x = virtual_width - size.x; } + view.w = size.x; - // BMP バッファ作成 - // 正しく描かれなかった時に分かりやすいよう初期値は濃いグレーにしておく - bmpbuf = new uint8 [view_width * view_height * 3]; - memset(bmpbuf, 0x70, view_width * view_height * 3); + // 下端も同様 + if (view.y > 0 && + (view.y + view.h >= virtual_height || + view.y + size.y >= virtual_height)) + { + view.y = virtual_height - size.y; + } + view.h = size.y; + + // スクロールバー(横)を再設定 + assert(hscroll); + int hpos = view.x; + int hthumbsize = view.w; + int hrange = virtual_width; + int hpagesize = view.w; + hscroll->SetScrollParam(hpos, hthumbsize, hrange, hpagesize); + + // スクロールバー(縦)を再設定 + assert(vscroll); + int vpos = view.y; + int vthumbsize = view.h; + int vrange = virtual_height; + int vpagesize = view.h; + vscroll->SetScrollParam(vpos, vthumbsize, vrange, vpagesize); } -// スクロールイベント +// スクロール位置変更通知 void -WXLunaBitmapPanel::OnScrollWin(wxScrollWinEvent& event) +WXLunaBitmapPanel::OnScroll(wxScrollEvent& event) { int orient = event.GetOrientation(); int pos = event.GetPosition(); if (orient == wxHORIZONTAL) { - view_x = pos * width_per_unit; + view.x = pos; } else { - view_y = pos * height_per_unit; + view.y = pos; } } +// タイマーイベント +void +WXLunaBitmapPanel::OnTimer(wxTimerEvent& event) +{ + Refresh(); +} + // XXX 実際にはこの辺かどこかで更新チェックをしないといけない。 -// 今は毎回全描画しているので画面サイズが大きくなると重くなる。 +// 今は表示領域を毎回描画しているので無駄。 -// 描画イベント (UI スレッドから呼ばれる) +// ビットマッププレーンの内容を描画する void -WXLunaBitmapPanel::OnPaint(wxPaintEvent& event) +WXLunaBitmapPanel::Draw() { - // 更新があれば、ここで裏バッファに描画 - Draw(); - // バッファから wxBitmap を作成 - wxImage image(view_width, view_height, bmpbuf, true); - wxBitmap bitmap(image); + assert(view.GetRight() < virtual_width); + assert(view.GetBottom() < virtual_height); - // 実画面 DC にコピー - wxPaintDC dstDC(this); - dstDC.DrawBitmap(bitmap, 0, 0); + const BitmapI8& src = renderer->GetComposite(); + + bitmap.DrawBitmap(0, 0, src, &pal[0], view); } -// ビットマッププレーンの内容をパネルのバックグラウンドバッファに描画する +// プレーン選択変更 void -WXLunaBitmapPanel::Draw() +WXLunaBitmapPanel::EnablePlane(int plane, bool value) +{ + if (value) { + planemask |= 1U << plane; + } else { + planemask &= ~(1U << plane); + } + + // パレット生成 + GenPalette(); +} + +// パレット適用変更 +void +WXLunaBitmapPanel::EnablePalette(bool value) { - int width = std::min(2048, view_width); - int height = std::min(1024, view_height); - // X 方向は32bit(ロングワード)単位で処理 - int x_offset = view_x / width_per_unit; - - for (int y = 0; y < height; y++) { - const uint32 *s = (uint32 *)&src[(view_y + y) * 256]; - uint8 *d = &bmpbuf[y * (width * 3)]; - - // dst を1ラインクリアしてから.. - memset(d, 0, width * 3); - // ON になってるドットだけ打っていく - for (int x = 0; x < width / 32; x++) { - uint32 srcdata = s[x_offset + x]; - for (int i = 0; srcdata != 0; srcdata <<= 1, i++) { - uint8 dst; - if ((int32)srcdata < 0) { - dst = 0xff; - } else { - dst = 0; - } - d[(x * 32 + i) * 3 + 0] = dst; - d[(x * 32 + i) * 3 + 1] = dst; - d[(x * 32 + i) * 3 + 2] = dst; + apply_palette = value; + + // パレット生成 + GenPalette(); +} + +// VM からのパレット変更通知 +void +WXLunaBitmapPanel::OnPaletteChanged(wxCommandEvent& event) +{ + // パレット生成 + GenPalette(); +} + +// パレット生成 +void +WXLunaBitmapPanel::GenPalette() +{ + if (apply_palette) { + // パレットを適用する場合 + // (今のところ BT454 (4bpp) のみ対応) + const ColorXBGR *vmpal = gBT454->GetHostPalette(); + const Color bg = vmpal[0].ToColor(); + for (int i = 0; i < pal.size(); i++) { + if ((i & planemask)) { + int n = i % 16; + pal[i] = vmpal[n].ToColor(); + } else { + pal[i] = bg; + } + } + } else { + // パレットを適用しない場合 + for (int i = 0; i < pal.size(); i++) { + if ((i & planemask)) { + pal[i] = Color(255, 255, 255); + } else { + pal[i] = Color(0, 0, 0); } } } } + // // LUNA ビットマップウィンドウ // -// イベントテーブル wxBEGIN_EVENT_TABLE(WXLunaBitmapWindow, inherited) - EVT_TIMER(wxID_ANY, WXLunaBitmapWindow::OnTimer) + EVT_COMMAND_RANGE(ID_PLANE0, ID_PLANE7, wxEVT_CHECKBOX, + WXLunaBitmapWindow::OnPlane) + EVT_CHECKBOX(ID_PALETTE, WXLunaBitmapWindow::OnApplyPalette) wxEND_EVENT_TABLE() // コンストラクタ -WXLunaBitmapWindow::WXLunaBitmapWindow(wxWindow *parent, wxWindowID id, - const wxString& name) - : inherited(parent, id, name, +WXLunaBitmapWindow::WXLunaBitmapWindow(wxWindow *parent, const wxString& name) + : inherited(parent, wxID_ANY, name, inherited::DEFAULT_STYLE | wxRESIZE_BORDER) { - panel = new WXLunaBitmapPanel(this); + auto *topsizer = new wxBoxSizer(wxVERTICAL); + + // 上にコントロールパネル(のまず下敷き) + auto ctrlpanel = new WXBitmapPanel(this); + topsizer->Add(ctrlpanel, 0, wxEXPAND); + + // コントロールパネル用の横 Sizer + auto *ctrlbox = new wxBoxSizer(wxHORIZONTAL); + + // プレーン選択チェックボックス (#3, #2, #1, #0 の順に並べる) + for (int i = planesw.size() - 1; i >= 0; i--) { + planesw[i] = new wxCheckBox(ctrlpanel, ID_PLANE(i), + string_format("#%d", i)); + ctrlbox->Add(planesw[i]); + } - // 大きさをそれに固定 - Layout(); - Fit(); + // パレット合成チェックボックス + applysw = new wxCheckBox(ctrlpanel, ID_PALETTE, _("Apply palette")); + ctrlbox->Add(applysw); + + // sizer と下敷きパネルを紐付ける + ctrlpanel->SetSizer(ctrlbox); + ctrlbox->SetSizeHints(ctrlpanel); + + viewpanel = new WXLunaBitmapPanel(this); + topsizer->Add(viewpanel, 1, wxEXPAND); + + SetSizer(topsizer); + + // 最小サイズ + wxSize minsize = ctrlpanel->GetMinClientSize(); + minsize.y += viewpanel->GetMinClientSize().y; + SetMinClientSize(minsize); + + // 1bpp ならプレーン選択はすべて無効、4bpp ならすべて有効。 + if (gPlaneVRAM->GetPlaneCount() == 1) { + for (int i = 0; i < planesw.size(); i++) { + planesw[i]->Enable(false); + } + } - // 更新タイマーは適当な短さにしておく - timer.SetOwner(this); - timer.Start(10); + // 初期値をコントロールにセットする + if (sticky_used) { + // 前回の値があればそれを使う。 + for (int i = 0; i < planesw.size(); i++) { + planesw[i]->SetValue(sticky_plane[i]); + } + applysw->SetValue(sticky_apply); + } else if (gPlaneVRAM->GetPlaneCount() == 1) { + // 初回 1bpp なら #0 プレーンのみオン。 + for (int i = 0; i < planesw.size(); i++) { + planesw[i]->SetValue((i == 0)); + } + applysw->SetValue(true); + } else { + // 初回 4bpp なら全プレーンオン。 + for (int i = 0; i < planesw.size(); i++) { + planesw[i]->SetValue(true); + } + applysw->SetValue(true); + } + + // SetValue() ではイベントが飛ばないので、 + // 直接呼び出してイベントが来たことにする。うーん…。 + for (int i = 0; i < planesw.size(); i++) { + DoPlane(i, planesw[i]->IsChecked()); + } + DoApplyPalette(applysw->IsChecked()); } // デストラクタ @@ -164,10 +347,59 @@ WXLunaBitmapWindow::~WXLunaBitmapWindow( { } -// タイマーイベント void -WXLunaBitmapWindow::OnTimer(wxTimerEvent& event) +WXLunaBitmapWindow::FontChanged() +{ + // 親 (wxSubWindow) の FontChanged() を実行しないようにするための + // 空のオーバーライド。親の FontChanged() が呼ぶ DoSize() が + // ウィンドウサイズを変更しようとするが、ここはテキストフォントを + // 持っていないのでその操作は不要。うーん。 +} + +// プレーン選択のチェックボックスイベント +void +WXLunaBitmapWindow::OnPlane(wxCommandEvent& event) +{ + int plane = event.GetId() - ID_PLANE0; + bool value = event.IsChecked(); + + DoPlane(plane, value); +} + +// プレーン選択 ON/OFF 処理 +void +WXLunaBitmapWindow::DoPlane(int plane, bool value) { - // モニタースクリーンを更新して、更新があれば画面を再描画 - panel->Refresh(); + // コントロールに指示 + viewpanel->EnablePlane(plane, value); + + // 値を保存 + sticky_plane[plane] = value; + sticky_used = true; } + +// パレット合成のチェックボックスイベント +void +WXLunaBitmapWindow::OnApplyPalette(wxCommandEvent& event) +{ + bool value = event.IsChecked(); + + DoApplyPalette(value); +} + +// パレット合成 ON/OFF 処理 +void +WXLunaBitmapWindow::DoApplyPalette(bool value) +{ + // コントロールに指示 + viewpanel->EnablePalette(value); + + // 値を保存 + sticky_apply = value; + sticky_used = true; +} + +// static 変数 +std::array WXLunaBitmapWindow::sticky_plane; +bool WXLunaBitmapWindow::sticky_apply; +bool WXLunaBitmapWindow::sticky_used;