|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2021 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // 独自スクロールバー
9: //
10:
11: // GTK のスクロールバーが色々残念なので、独自コントロールを作る。
12: //
13: // pos、thumbsize、range、pagesize は wxScrollBar のそれと同じなのでそちら参照。
14: // 例えば全体で100要素あるうち画面に10要素分だけ表示できる構成の場合、
15: // range = 100、thumbsize = 10 となる。表示位置は、一番上(左)の要素から表示
16: // している場合は pos = 0、一番下(右)の要素まで表示している場合は pos = 90。
17: //
18: // 0 10 90 100
19: // +---+-----------------+---+
20: //
21: // ピクセル座標では、上(左)に 2ピクセル、下(右)に1ピクセルのフチがある。
22: // そのため range として使える領域は bitmap.GetHeight() (または GetWidth())
23: // から 3 引いたサイズになる。
24: //
25: // 左フチ つまみ 右フチ
26: // +--+ ●----○ ++
27: // 0 1 p1 p2 h-1
28: // 明□□□□□□□□□明
29: // 明□□■■ □□□□明
30: // 明□□■■ □□□□明
31: // 明□□ □□□□明
32: // 明明明明明明明明明明明
33: //
34: // 明明明明明 0 <- 上フチ
35: // □□□□明 1 <- 上フチ
36: // □□□□明
37: // □■■ 明 p1 ●
38: // □■■ 明 | つまみ
39: // □ 明 |
40: // □□□□明 p2 ○
41: // □□□□明
42: // □□□□明
43: // 明明明明明 h-1 <- 下フチ
44: //
45: // 明 = UD_LIGHT_GREY
46: // □ = UD_GREY
47: // ■ = BGPANEL
48: // = UD_BLACK
49:
50: #include "wxscrollbar.h"
51: #include "wxcolor.h"
52:
53: //#define SCROLL_DEBUG 1
54:
55: #if defined(SCROLL_DEBUG)
56: #define DPRINTF(fmt...) printf(fmt)
57: #else
58: #define DPRINTF(fmt...) /**/
59: #endif
60:
61: #define BORDER_LT (2) // 上(左) 側のフチのピクセル数
62: #define BORDER_RB (1) // 下(右) 側のフチのピクセル数
63:
64: // 通知イベント。
65: // このコントロールがつまみの位置を変更した場合にイベントが発生する。
66: // SetThumbPosition() などでコードが能動的に変更した場合は発生しない。
67: // イベントの内容は概ね wxScrollEvent 準拠のはず。
68: // ただし、引数は wxScrollEvent だが、イベントタイプは EVT_SCROLL_* では
69: // なく wxCommandEvent の NONO_EVT_SCROLL を使用していること、
70: // wxScrollBar と異なり自動的に親かだれかにイベントが飛んでこないので
71: // 必要な人が適宜 Connect() する必要がある、ところが異なる。
72: wxDEFINE_EVENT(NONO_EVT_SCROLL, wxScrollEvent);
73:
74: // イベントテーブル
75: wxBEGIN_EVENT_TABLE(WXScrollBar, inherited)
76: EVT_SIZE(WXScrollBar::OnSize)
77: EVT_MOUSE_EVENTS(WXScrollBar::OnMouse)
1.1.1.2 root 78: EVT_TIMER(wxID_ANY, WXScrollBar::OnTimer)
1.1 root 79: wxEND_EVENT_TABLE()
80:
81: // コンストラクタ
82: //
83: // style は wxSB_VERTICAL か wxSB_HORIZONTAL。
84: // 本家の wxScrollBar は style のデフォルトが wxSB_HORIZONTAL だが、
85: // うちではデフォルト引数にはしない。
86: WXScrollBar::WXScrollBar(wxWindow *parent, wxWindowID id, long style)
1.1.1.2 root 87: : inherited(parent, id, wxDefaultPosition, wxSize(16, 16), 0)
1.1 root 88: {
89: if (style == wxSB_VERTICAL) {
90: is_vertical = true;
91: }
1.1.1.2 root 92:
1.1.1.3 ! root 93: FontChanged();
! 94:
1.1.1.2 root 95: timer.SetOwner(this);
1.1 root 96: }
97:
98: // デストラクタ
99: WXScrollBar::~WXScrollBar()
100: {
101: }
102:
1.1.1.3 ! root 103: // フォントサイズ変更
! 104: void
! 105: WXScrollBar::FontChanged()
! 106: {
! 107: inherited::FontChanged();
! 108:
! 109: int newpx;
! 110: switch (font_height) {
! 111: case 12:
! 112: newpx = 16;
! 113: break;
! 114: case 16:
! 115: newpx = 20;
! 116: break;
! 117: case 24:
! 118: newpx = 32;
! 119: break;
! 120: default:
! 121: PANIC("Unexpected font_height=%d", font_height);
! 122: }
! 123:
! 124: // つまみの最小サイズ。適当に幅の半分。
! 125: thumbmin = newpx / 2;
! 126:
! 127: // コントロールの大きさを変更
! 128: wxSize size = GetClientSize();
! 129: if (is_vertical) {
! 130: size.x = newpx;
! 131: } else {
! 132: size.y = newpx;
! 133: }
! 134: SetClientSize(size);
! 135: SetMinClientSize(size);
! 136: }
! 137:
1.1 root 138: // スクロールバーのパラメータを設定する。
139: // 通知イベントは送出しない。
140: //
141: // 各パラメータは wxScrollBar::SetScrollbar() 準拠のはずなのでそちら参照。
142: // SetScrollbar() は wxScrollBar ではなく wxWindow の仮想関数で、ここで定義
143: // すると微妙に型が違うオーバーライドになってしまうため、名前を変えてある。
144: void
145: WXScrollBar::SetScrollParam(int pos_, int thumbsize_, int range_, int pagesize_)
146: {
147: DPRINTF("SetScrollParam: pos=%d thumbsize=%d range=%d pagesize=%d\n",
148: pos_, thumbsize_, range_, pagesize_);
149:
150: thumbsize = thumbsize_;
151: range = range_;
152: pagesize = pagesize_;
153: SetThumbPosition(pos_);
154: }
155:
156: // つまみの位置を設定する。外部インタフェース。
157: // 通知イベントは送出しない。
1.1.1.3 ! root 158: // つまみが移動すれば true を、移動しなければ false を返す。
! 159: bool
1.1 root 160: WXScrollBar::SetThumbPosition(int pos)
161: {
162: // パラメータセット前に OnSize されるとここに来る
163: if (range < 1) {
1.1.1.3 ! root 164: return false;
! 165: }
! 166:
! 167: // 単位位置の範囲チェック
! 168: if (pos < 0) {
! 169: pos = 0;
! 170: } else if (pos > range - thumbsize) {
! 171: pos = range - thumbsize;
1.1 root 172: }
173:
174: // 通常の描画座標は単位値とピクセル数の単純な比で求まる。
175: //
176: // 0 10 90 100 [unit]
177: // +---+-----------------+---+
178: //
179: // 0 20 180 200 [px]
180: // +---+-----------------+---+
181: //
182: // つまみが描画領域に対して小さすぎる場合、ある程度より小さくならないよう
1.1.1.3 ! root 183: // に抑制する (thumbmin [px])。
1.1 root 184: // 例えばここで描画範囲が 50px の場合、つまみの最小サイズは 8px なので
185: // 単位可動域 0-90 を 0-42 ピクセルにマッピングし直して描画する。
186: //
187: // 0 5 8 42 45 50 [px]
188: // +---+-+-------------+-+---+
189:
190: // つまみが小さくなりすぎるのを防ぐ
191: int thumbsize_px = thumbsize * range_px / range;
1.1.1.3 ! root 192: if (thumbsize_px < thumbmin) {
! 193: thumbsize_px = thumbmin;
1.1 root 194: }
195:
196: // range はスクロールバー領域全体の長さ (図の 100)。
197: // mrange はつまみの大きさを含まない可動域?の長さ (図の 90) とする。
198: // [p1, p2) がつまみのピクセル座標。
1.1.1.3 ! root 199: int oldp1 = p1;
! 200: int oldp2 = p2;
1.1 root 201: int mrange = range - thumbsize;
202: if (mrange == 0) {
203: // つまみが全体を占めていると可動域 mrange が 0 になる。
204: p1 = 0;
205: } else {
206: int mrange_px = range_px - thumbsize_px;
1.1.1.3 ! root 207: p1 = pos * mrange_px / mrange;
1.1 root 208: }
209: p2 = p1 + thumbsize_px;
210:
211: // 可動域の上(左)に2ピクセルの枠がある
212: p1 += BORDER_LT;
213: p2 += BORDER_LT;
214:
1.1.1.3 ! root 215: // 移動していない (表示位置が変わっていない) なら false で帰る。
! 216: // 目一杯を占めていて移動できないつまみをドラッグしてマウス移動
! 217: // しても、つまみが移動しないのだから、スクロールも起こさないため。
! 218: DPRINTF("SetThumbPosition: oldpos=%d newpos=%d oldp=%d-%d p=%d-%d\n",
! 219: thumbpos, pos, oldp1, oldp2, p1, p2);
! 220: if (p1 == oldp1 && p2 == oldp2) {
! 221: return false;
! 222: }
1.1 root 223:
1.1.1.3 ! root 224: thumbpos = pos;
! 225: DPRINTF("SetThumbPosition: new pos=%d\n", thumbpos);
1.1 root 226: Refresh();
1.1.1.3 ! root 227: return true;
1.1 root 228: }
229:
230: // つまみの位置を pos に設定し、移動すれば通知イベントを送出する。(内部用)
231: void
232: WXScrollBar::MoveThumb(int pos)
233: {
1.1.1.3 ! root 234: if (SetThumbPosition(pos)) {
1.1 root 235: // Orientation は wx"SB_"VERTICAL ではなく wxVERTICAL のほう。
236: // (値は同じっぽいからいいけど、どうしてこうなった…)
237: int orient = is_vertical ? wxVERTICAL : wxHORIZONTAL;
238: wxScrollEvent newev(NONO_EVT_SCROLL, GetId(), thumbpos, orient);
239: newev.SetEventObject(this);
240: AddPendingEvent(newev);
241: }
242: }
243:
1.1.1.3 ! root 244: // コントロールの主方向(スクロール方向)の最小ピクセル数を返す
! 245: int
! 246: WXScrollBar::GetMinLength() const
! 247: {
! 248: return thumbmin + BORDER_LT + BORDER_RB;
! 249: }
! 250:
1.1 root 251: // サイズイベント
252: void
253: WXScrollBar::OnSize(wxSizeEvent& event)
254: {
255: auto s __unused = event.GetSize();
256: DPRINTF("OnSize %d,%d\n", s.x, s.y);
257:
258: // 親クラス
259: inherited::OnSize(event);
260:
261: if (is_vertical) {
262: range_px = bitmap.GetHeight() - BORDER_LT - BORDER_RB;
263: } else {
264: range_px = bitmap.GetWidth() - BORDER_LT - BORDER_RB;
265: }
266:
267: // range_px が変わったので再描画
1.1.1.3 ! root 268: SetThumbPosition(thumbpos);
1.1 root 269: }
270:
271: // マウスイベント
272: void
273: WXScrollBar::OnMouse(wxMouseEvent& event)
274: {
275: event.Skip();
276:
1.1.1.2 root 277: // 現在位置
278: wxPoint pos = event.GetPosition();
279: if (is_vertical) {
280: mouse_px = pos.y;
281: } else {
282: mouse_px = pos.x;
283: }
284: DPRINTF("OnMouse: mouse_px=%d\n", mouse_px);
285:
1.1 root 286: int wheel = event.GetWheelRotation();
287: if (wheel != 0) {
288: DPRINTF("OnMouse: wheel=%d\n", wheel);
289:
290: // ホイールは1回で +-120 が飛んでくる。
291: // どこのルールか分からんけどだいたい3行分の移動としているようなので
292: // ここでも踏襲する。
293: MoveThumb(thumbpos - wheel / 40);
294: }
295:
1.1.1.2 root 296: if (event.LeftUp()) {
297: left_click = false;
298: }
299:
1.1 root 300: if (event.LeftDown() || event.LeftDClick()) {
1.1.1.2 root 301: left_click = true;
302:
1.1 root 303: // 左クリック。間隔が速いとダブルクリックになる。
304: // ダブルクリックは LeftDown, LeftUp, LeftDClick, LeftUp の順で来る
305: // ので、LeftDown と LeftDClick を同列で扱っておく。
1.1.1.2 root 306: int r = PosCmp();
1.1 root 307: if (r == 0) {
308: // つまみ内左クリックでは何も起きないが、
309: // ドラッグ用に左クリック開始時点の位置を保存。
310: start_thumb = thumbpos;
1.1.1.2 root 311: start_px = mouse_px;
312: DPRINTF("OnMouse: LeftDown thumbpos=%d start_px=%d\n",
1.1 root 313: start_thumb, start_px);
314: } else {
315: // つまみ外の下地部分ならページアップ、ページダウン
316: DPRINTF("OnMouse: LeftDown cmp=%d\n", r);
317: MoveThumb(thumbpos + r * pagesize);
318: }
1.1.1.2 root 319:
320: // 押しっぱなし判定のためタイマーを開始。
321: timer.Start(500);
1.1 root 322: }
323:
324: if (event.Dragging()) {
325: // ドラッグ開始時からの差分を求めて..
1.1.1.2 root 326: int delta = mouse_px - start_px;
1.1 root 327: DPRINTF("OnMouse: Dragging delta=%d\n", delta);
328:
329: // ピクセルでの移動量を変換
330: MoveThumb(start_thumb + delta * range / range_px);
331: }
332: }
333:
1.1.1.2 root 334: // タイマーイベント
335: void
336: WXScrollBar::OnTimer(wxTimerEvent& event)
1.1 root 337: {
1.1.1.2 root 338: int r = PosCmp();
339: if (left_click && r != 0) {
340: // つまみ外で左クリックが続いていたらもう1回動かす
341: MoveThumb(thumbpos + r * pagesize);
1.1 root 342:
1.1.1.2 root 343: // キーリピートと同じ要領で2回目以降は短くする。
344: timer.Start(100);
1.1 root 345: } else {
1.1.1.2 root 346: // つまみ内か左クリックが終わっていたら終了
347: timer.Stop();
1.1 root 348: }
1.1.1.2 root 349: }
1.1 root 350:
1.1.1.2 root 351: // mouse_px がつまみ内なら 0 を、つまみより上(左)なら -1、下(右)なら 1 を返す。
352: int
353: WXScrollBar::PosCmp() const
354: {
355: if (mouse_px < p1) {
1.1 root 356: return -1;
357: }
1.1.1.2 root 358: if (mouse_px > p2) {
1.1 root 359: return 1;
360: }
361: return 0;
362: }
363:
364: // 描画
365: void
366: WXScrollBar::Draw()
367: {
368: const int& w = bitmap.GetWidth();
369: const int& h = bitmap.GetHeight();
370:
371: if (is_vertical) {
372: // 背景
373: bitmap.DrawLineH(UD_LIGHT_GREY, 0, 0, w);
374: bitmap.DrawLineH(UD_LIGHT_GREY, 0, h - 1, w);
375: bitmap.DrawLineV(UD_LIGHT_GREY, w - 1, 1, h - 1);
376: bitmap.FillRect(UD_GREY, 0, 1, w - 1, h - 2);
377:
378: // つまみ
379: bitmap.FillRect(BGPANEL, 1, p1, w - 4, p2 - p1 - 1);
380: bitmap.DrawLineH(UD_BLACK, 1, p2 - 1, w - 1);
381: bitmap.DrawLineV(UD_BLACK, w - 2, p1, p2);
382: } else {
383: // 背景
384: bitmap.DrawLineV(UD_LIGHT_GREY, 0, 0, h);
385: bitmap.DrawLineV(UD_LIGHT_GREY, w - 1, 0, h);
386: bitmap.DrawLineH(UD_LIGHT_GREY, 1, h - 1, w - 1);
387: bitmap.FillRect(UD_GREY, 1, 0, w - 2, h - 1);
388:
389: // つまみ
390: bitmap.FillRect(BGPANEL, p1, 1, p2 - p1 - 1, h - 4);
391: bitmap.DrawLineV(UD_BLACK, p2 - 1, 1, h - 1);
392: bitmap.DrawLineH(UD_BLACK, p1, h - 2, p2);
393: }
394: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.