File:  [Isaki's NoNo m68k/m88k emulator] / nono / wx / wxscreenmonitor.cpp
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// スクリーンモニタ
//

#include "wxscreenmonitor.h"
#include "wxscrollbar.h"
#include "wxtextscreen.h"

//#define WINDOW_DEBUG 1

#if defined(WINDOW_DEBUG)
#define DPRINTF(fmt...) printf(fmt)
#else
#define DPRINTF(fmt...) /**/
#endif

//
// スクリーンパネル (共通部)
//

// イベントテーブル
wxBEGIN_EVENT_TABLE(WXScreenPanel, inherited)
	EVT_MOTION(WXScreenPanel::OnMouseMove)
	EVT_LEAVE_WINDOW(WXScreenPanel::OnMouseMove)
	EVT_MOUSEWHEEL(WXScreenPanel::OnMouseWheel)
wxEND_EVENT_TABLE()

// コンストラクタ
WXScreenPanel::WXScreenPanel(wxWindow *parent)
	: inherited(parent)
{
	cursor = wxDefaultPosition;
}

// 継承側でサイズが確定したところで呼ぶ、コンストラクタの残り。
void
WXScreenPanel::Ctor(int plane_width_, int plane_height_)
{
	// 画面のネイティブの大きさ。
	plane_width  = plane_width_;
	plane_height = plane_height_;

	// まず等倍でセット。
	scale = 1;
	SetScale(1);
}

// デストラクタ
WXScreenPanel::~WXScreenPanel()
{
}

// 倍率を設定
void
WXScreenPanel::SetScale(int new_scale)
{
	int before = scale;

	scale = new_scale;

	// 仮想画面の大きさを計算。
	virtual_width  = plane_width  * scale;
	virtual_height = plane_height * scale;

	// ビューのスケール変換は前後のスケールがいるのでここでやる。
	view.x = view.x * scale / before;
	view.y = view.y * scale / before;
	view.w = view.w * scale / before;
	view.h = view.h * scale / before;

	SetMaxSize(wxSize(virtual_width, virtual_height));
}

// スクロール位置変更通知
void
WXScreenPanel::OnScroll(wxScrollEvent& event)
{
	int orient = event.GetOrientation();
	int pos = event.GetPosition();

	if (orient == wxHORIZONTAL) {
		view.x = pos;
	} else {
		view.y = pos;
	}
}

// マウス移動イベント
void
WXScreenPanel::OnMouseMove(wxMouseEvent& event)
{
	// pt はこのパネル左上からの位置
	const wxPoint& pt = event.GetPosition();

	// pt がパネル内の座標なのに Leaving() になることがある。
	// pt がパネル外の座標なのに Leaving() にならないことがある。
	if (event.Leaving() == false && GetClientRect().Contains(pt)) {
		// cursor は(拡大前の)仮想画面上の座標
		cursor.x = (view.x + pt.x) / scale;
		cursor.y = (view.y + pt.y) / scale;
		assertmsg(cursor.x < plane_width,  "%d < %d", cursor.x, plane_width);
		assertmsg(cursor.y < plane_height, "%d < %d", cursor.y, plane_height);
	} else {
		cursor = wxDefaultPosition;
	}
}

// マウスホイールイベント
void
WXScreenPanel::OnMouseWheel(wxMouseEvent& event)
{
	// ホイールは1回で ±120 が飛んでくる。
	// ここでは 64 ドットずつ移動させてみる。
	int delta = event.GetWheelRotation() / 30 * 16;
	int y = view.y - delta;
	if (y < 0) {
		y = 0;
	} else {
		int max_y = virtual_height - view.h;
		if (y >= max_y) {
			y = max_y;
		}
	}
	view.y = y;

	// マウスカーソルは移動してないが view が動いたので cursor 再計算。
	OnMouseMove(event);

	// 縦スクロールバーの位置を追従。
	auto window = dynamic_cast<WXScreenWindow *>(GetParent());
	window->SetVScrollPosition(view.y);
}


//
// スクリーンモニタウィンドウ (共通部)
//

// +----------------------------------------------------+
// | wxPanel *ctrlpanel                                 |
// | (wx製のコントロールパネル、横目一杯まで伸びる)     |
// +---------------------------+------------------------+
// | WXTextScreen *statuspanel | spacer                 |
// | (固定幅のテキスト情報)    | (必要なら空き地をおく) |
// +---------------------------+-+----------------------+
// | WXScreenPanel *viewpanel    | WXScrollBar *vscroll |
// | (VRAM 表示パネル)           | (縦スクロールバー)   |
// +-----------------------------+----------------------+
// | WXScrollBar *hscroll        | corner               |
// | (横スクロールバー)          | (空き地)             |
// +-----------------------------+----------------------+

// イベントテーブル
wxBEGIN_EVENT_TABLE(WXScreenWindow, inherited)
	EVT_TIMER(wxID_ANY, WXScreenWindow::OnTimer)
wxEND_EVENT_TABLE()

// コンストラクタ
WXScreenWindow::WXScreenWindow(wxWindow *parent, const wxString& name)
	: inherited(parent, wxID_ANY, name, DEFAULT_STYLE | wxRESIZE_BORDER)
{
	SetName(name + ".Window");

	// 上段、コントロールパネル(のまず下敷き)
	ctrlpanel = new wxPanel(this);
	ctrlpanel->SetName(name + ".CtrlPanel");

	// 中段、情報パネルと右側の空き地用。
	// 情報パネルは継承クラスで。
	spacer = new WXBitmapPanel(this);

	// 下段、表示パネルは継承クラスで。

	// スクロールバー。
	vscroll = new WXScrollBar(this, wxID_ANY, wxSB_VERTICAL);
	hscroll = new WXScrollBar(this, wxID_ANY, wxSB_HORIZONTAL);

	// 右下の空き地。
	corner = new WXBitmapPanel(this);

	// 更新タイマーはモニターパネル同様の 30Hz にしておく。
	timer.SetOwner(this);
	timer.Start(33);

	DPRINTF("%s.ctor end\n", __func__);
}

// コンストラクタの残り。
// statuspanel、viewpanel を作成したあとで呼ぶ。
void
WXScreenWindow::Ctor()
{
	assert(statuspanel);
	assert(viewpanel);

	// 自前レイアウト。
	SetMyLayout();

	FontChanged();

	// スクロールバーからの通知イベントは表示パネルへ回す。
	vscroll->Bind(NONO_EVT_SCROLL, &WXScreenPanel::OnScroll, viewpanel);
	hscroll->Bind(NONO_EVT_SCROLL, &WXScreenPanel::OnScroll, viewpanel);
}

// デストラクタ
WXScreenWindow::~WXScreenWindow()
{
}

bool
WXScreenWindow::GetMySizeHints(wxSize *newp, wxSize *minp, wxSize *maxp,
	wxSize *incp)
{
	DPRINTF("Window.%s\n", __func__);

	// viewpanel の最小サイズは縦横スクロールバーの大きさで決まる。
	const wxSize& ctrlsize = ctrlpanel->GetSize();
	const wxSize& ctrlmin  = ctrlpanel->GetMinSize();
	const wxSize& statsize = statuspanel->GetSize();
	const wxSize& statmin  = statuspanel->GetMinSize();
	const wxSize& viewsize = viewpanel->GetSize();
	const wxSize& vscrsize = vscroll->GetSize();
	const wxSize& vscrmin  = vscroll->GetMinSize();
	const wxSize& hscrsize = hscroll->GetSize();

	// コントロールからウィンドウサイズを計算。
	int size_x = std::max(ctrlmin.x, statmin.x);
	size_x = std::max(size_x, viewsize.x + vscrsize.x);
	int size_y = ctrlsize.y + statsize.y + viewsize.y + hscrsize.y;
	wxSize newsize(size_x, size_y);

	// 最小サイズを計算。
	int min_x = std::max(ctrlmin.x, statmin.x);
	// statmin.x は viewpanel の最小幅より必ず大きいのでこれは不要。
	//min_x = std::max(min_x, hscrmin.x + vscrsize.x);
	int min_y = ctrlmin.y + statmin.y + vscrmin.y + hscrsize.y;
	wxSize minsize(min_x, min_y);

	// 最大サイズは viewsize に依存しそうだから手抜き。
	const wxSize& viewmax = viewpanel->GetMaxSize();
	int max_x = viewmax.x;
	if (max_x > 0) {
		max_x += vscrsize.x;
	}
	int max_y = viewmax.y;
	if (max_y > 0) {
		max_y += ctrlmin.y + statmin.y + hscrsize.y;
	}
	wxSize maxsize(max_x, max_y);

	if (newp) *newp = newsize;
	if (minp) *minp = minsize;
	if (maxp) *maxp = maxsize;
	if (incp) *incp = wxDefaultSize;
	return true;
}

bool
WXScreenWindow::Layout()
{
	if (MyLayout() == false) {
		DPRINTF("%s.Layout false\n", __func__);
		// sizer があるので呼ばないといけない。
		BaseLayout();

		// コントロールを再配置。
		const wxSize client = GetClientSize();
		const wxSize& ctrlsize = ctrlpanel->GetSize();
		const wxSize& statsize = statuspanel->GetSize();
		const wxSize& vscrsize = vscroll->GetSize();
		const wxSize& hscrsize = hscroll->GetSize();
		DPRINTF("%s.Layout client=(%d,%d)\n", __func__, client.x, client.y);
		DPRINTF("%s.Layout vscrsize=(%d,%d) hscrsize=(%d,%d)\n", __func__,
			vscrsize.x, vscrsize.y, hscrsize.x, hscrsize.y);
		ctrlpanel->SetSize(0, 0, client.x, ctrlsize.y);
		statuspanel->SetSize(0, ctrlsize.y, statsize.x, statsize.y);
		int view_y = ctrlsize.y + statsize.y;
		int view_w = client.x - vscrsize.x;
		int view_h = client.y - ctrlsize.y - statsize.y - hscrsize.y;
		viewpanel->SetSize(0, view_y, view_w, view_h);
		vscroll->SetSize(view_w, view_y, vscrsize.x, view_h);
		hscroll->SetSize(0, view_y + view_h, view_w, hscrsize.y);
		corner->SetSize(view_w, view_y + view_h, hscrsize.x, vscrsize.y);
		DPRINTF("%s.Layout view=(%d,%d,%d,%d)\n", __func__,
			viewpanel->GetPosition().x,
			viewpanel->GetPosition().y,
			viewpanel->GetSize().x,
			viewpanel->GetSize().y);

		// ステータスパネル右に空き地が出来るなら埋める。
		if (statsize.x < client.x) {
			spacer->SetSize(statsize.x, ctrlsize.y,
				client.x - statsize.x, statsize.y);
			spacer->Show();
		} else {
			spacer->Hide();
		}

		SetScroll();

		DPRINTF("%s.Layout false done\n", __func__);
	}

	return true;
}

// 倍率変更イベント
void
WXScreenWindow::OnScale(wxCommandEvent& event)
{
	int scale_index = event.GetSelection();

	DoScale(scale_index);
}

// 倍率変更処理
void
WXScreenWindow::DoScale(int scale_index)
{
	DPRINTF("%s index=%d\n", __method__, scale_index);
	int scale_ = 1 << scale_index;
	viewpanel->SetScale(scale_);

	SetScroll();

	wxSize maxsize;
	GetMySizeHints(NULL, NULL, &maxsize, NULL);
	SetMaxClientSize(maxsize);
}

// タイマーイベント
void
WXScreenWindow::OnTimer(wxTimerEvent& event)
{
	viewpanel->Refresh();

	// この位置の情報で screen を更新
	TextScreen& screen = statuspanel->GetScreen();
	const wxPoint& cursor = viewpanel->GetCursor();

	// UpdateInfo() は cursor が範囲外の時の描画も行っている。
	UpdateInfo(screen, cursor.x, cursor.y);
	statuspanel->Refresh();
}

// 縦スクロールバーの位置を設置する。
// (パネルからマウスホイールイベントで呼ばれる)
void
WXScreenWindow::SetVScrollPosition(int pos)
{
	vscroll->SetThumbPosition(pos);
}

// サイズ変更処理。パネルサイズと倍率が変わったら呼ぶこと。
// 倍率が変わった場合もスクロールバーの再設定とかがあるため。
void
WXScreenWindow::SetScroll()
{
	wxSize viewsize = viewpanel->GetSize();
	int virtual_width  = viewpanel->GetVirtualWidth();
	int virtual_height = viewpanel->GetVirtualHeight();
	Rect& view = viewpanel->view;

	DPRINTF("%s: viewsize=(%d,%d)\n", __method__, viewsize.x, viewsize.y);

	// スクロールバーが右端まで行っている場合は右を固定したように見せる。
	// ただし全体が見えている場合は除く。

	if (view.x > 0 &&
	    (view.x + view.w == virtual_width ||
	     view.x + viewsize.x >= virtual_width))
	{
		view.x = virtual_width - viewsize.x;
		if (view.x < 0) {
			view.x = 0;
		}
	}
	view.w = viewsize.x;

	// 下端も同様
	if (view.y > 0 &&
	    (view.y + view.h >= virtual_height ||
	     view.y + viewsize.y >= virtual_height))
	{
		view.y = virtual_height - viewsize.y;
		if (view.y < 0) {
			view.y = 0;
		}
	}
	view.h = viewsize.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);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.