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