|
|
nono 0.3.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// ビデオ系モニターウィンドウ
//
#include "wxvideomonitor.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 ビットマップパネル
//
// イベントテーブル
wxBEGIN_EVENT_TABLE(WXLunaBitmapPanel, inherited)
EVT_SIZE(WXLunaBitmapPanel::OnSize)
EVT_TIMER(wxID_ANY, WXLunaBitmapPanel::OnTimer)
wxEND_EVENT_TABLE()
// コンストラクタ
WXLunaBitmapPanel::WXLunaBitmapPanel(wxWindow *parent)
: inherited(parent, wxDefaultSize)
{
// スクロールバーを自分で持つ (配置は 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<LunaRenderer*>(gRenderer);
// 更新タイマーはモニターパネル同様の 30Hz にしておく。
timer.SetOwner(this);
timer.Start(33);
}
// デストラクタ
WXLunaBitmapPanel::~WXLunaBitmapPanel()
{
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;
}
// サイズ変更イベント
void
WXLunaBitmapPanel::OnSize(wxSizeEvent& event)
{
// Mac ではウィンドウ作成時に 0 以下の値が来ることがある
// GTK では 1 で来ることがある
wxSize size = event.GetSize();
if (size.x <= 1 || size.y <= 1) {
return;
}
inherited::OnSize(event);
// スクロールバー分を除いたところが描画領域
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;
// 下端も同様
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::OnScroll(wxScrollEvent& event)
{
int orient = event.GetOrientation();
int pos = event.GetPosition();
if (orient == wxHORIZONTAL) {
view.x = pos;
} else {
view.y = pos;
}
}
// タイマーイベント
void
WXLunaBitmapPanel::OnTimer(wxTimerEvent& event)
{
Refresh();
}
// XXX 実際にはこの辺かどこかで更新チェックをしないといけない。
// 今は表示領域を毎回描画しているので無駄。
// ビットマッププレーンの内容を描画する
void
WXLunaBitmapPanel::Draw()
{
assert(view.GetRight() < virtual_width);
assert(view.GetBottom() < virtual_height);
const BitmapI8& src = renderer->GetComposite();
bitmap.DrawBitmap(0, 0, src, &pal[0], view);
}
// プレーン選択変更
void
WXLunaBitmapPanel::EnablePlane(int plane, bool value)
{
if (value) {
planemask |= 1U << plane;
} else {
planemask &= ~(1U << plane);
}
// パレット生成
GenPalette();
}
// パレット適用変更
void
WXLunaBitmapPanel::EnablePalette(bool value)
{
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_COMMAND_RANGE(ID_PLANE0, ID_PLANE7, wxEVT_CHECKBOX,
WXLunaBitmapWindow::OnPlane)
EVT_CHECKBOX(ID_PALETTE, WXLunaBitmapWindow::OnApplyPalette)
wxEND_EVENT_TABLE()
// コンストラクタ
WXLunaBitmapWindow::WXLunaBitmapWindow(wxWindow *parent, const wxString& name)
: inherited(parent, wxID_ANY, name,
inherited::DEFAULT_STYLE | wxRESIZE_BORDER)
{
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]);
}
// パレット合成チェックボックス
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);
}
}
// 初期値をコントロールにセットする
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());
}
// デストラクタ
WXLunaBitmapWindow::~WXLunaBitmapWindow()
{
}
void
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)
{
// コントロールに指示
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<bool, WXLunaBitmapWindow::NPLANES> WXLunaBitmapWindow::sticky_plane;
bool WXLunaBitmapWindow::sticky_apply;
bool WXLunaBitmapWindow::sticky_used;
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.