--- nono/wx/wxscrollbar.cpp 2026/04/29 17:05:08 1.1 +++ nono/wx/wxscrollbar.cpp 2026/04/29 17:05:44 1.1.1.4 @@ -60,7 +60,6 @@ #define BORDER_LT (2) // 上(左) 側のフチのピクセル数 #define BORDER_RB (1) // 下(右) 側のフチのピクセル数 -#define THUMB_MIN (8) // つまみの最小サイズ (ピクセル) // 通知イベント。 // このコントロールがつまみの位置を変更した場合にイベントが発生する。 @@ -76,6 +75,7 @@ wxDEFINE_EVENT(NONO_EVT_SCROLL, wxScroll wxBEGIN_EVENT_TABLE(WXScrollBar, inherited) EVT_SIZE(WXScrollBar::OnSize) EVT_MOUSE_EVENTS(WXScrollBar::OnMouse) + EVT_TIMER(wxID_ANY, WXScrollBar::OnTimer) wxEND_EVENT_TABLE() // コンストラクタ @@ -84,11 +84,17 @@ wxEND_EVENT_TABLE() // 本家の wxScrollBar は style のデフォルトが wxSB_HORIZONTAL だが、 // うちではデフォルト引数にはしない。 WXScrollBar::WXScrollBar(wxWindow *parent, wxWindowID id, long style) - : inherited(parent, id, wxSize(16, 16), 0) + : inherited(parent, id) { + SetName("WXScrollBar"); + if (style == wxSB_VERTICAL) { is_vertical = true; } + + FontChanged(); + + timer.SetOwner(this); } // デストラクタ @@ -96,6 +102,58 @@ WXScrollBar::~WXScrollBar() { } +// フォントサイズ変更 +void +WXScrollBar::FontChanged() +{ + inherited::FontChanged(); + DPRINTF("%s %d\n", __method__, font_height); + + switch (font_height) { + case 12: + subpx = 16; + break; + case 16: + subpx = 20; + break; + case 24: + subpx = 32; + break; + default: + PANIC("Unexpected font_height=%d", font_height); + } + + // つまみの最小サイズ。適当に幅の半分。 + thumbmin = subpx / 2; + + Fit(); +} + +void +WXScrollBar::Fit() +{ + // 最小サイズを設定。 + wxSize minsize; + if (is_vertical) { + minsize.x = subpx; + minsize.y = font_height; + } else { + minsize.x = font_width * 2; + minsize.y = subpx; + } + DPRINTF("%s GetSize=(%d,%d) Best=(%d,%d)\n", __method__, + GetSize().x, GetSize().y, minsize.x, minsize.y); + SetSizeHints(minsize, wxDefaultSize); + + // 主方向は最小サイズを下回らなければ維持、 + // 副方向は常に規定サイズ。 + if (is_vertical) { + SetSize(subpx, GetSize().y); + } else { + SetSize(GetSize().x, subpx); + } +} + // スクロールバーのパラメータを設定する。 // 通知イベントは送出しない。 // @@ -116,29 +174,20 @@ WXScrollBar::SetScrollParam(int pos_, in // つまみの位置を設定する。外部インタフェース。 // 通知イベントは送出しない。 -void +// つまみが移動すれば true を、移動しなければ false を返す。 +bool WXScrollBar::SetThumbPosition(int pos) { - thumbpos = pos; - DPRINTF("SetThumbPosition: %d\n", pos); - - // 単位位置の範囲チェック - if (thumbpos < 0) { - thumbpos = 0; - } else if (thumbpos > range - thumbsize) { - thumbpos = range - thumbsize; + // パラメータセット前にリサイズされるとここに来る + if (range < 1) { + return false; } - RefreshThumb(); -} - -// つまみの位置を計算して再描画。 -void -WXScrollBar::RefreshThumb() -{ - // パラメータセット前に OnSize されるとここに来る - if (range < 1) { - return; + // 単位位置の範囲チェック + if (pos < 0) { + pos = 0; + } else if (pos > range - thumbsize) { + pos = range - thumbsize; } // 通常の描画座標は単位値とピクセル数の単純な比で求まる。 @@ -150,7 +199,7 @@ WXScrollBar::RefreshThumb() // +---+-----------------+---+ // // つまみが描画領域に対して小さすぎる場合、ある程度より小さくならないよう - // に抑制する (THUMB_MIN [px])。 + // に抑制する (thumbmin [px])。 // 例えばここで描画範囲が 50px の場合、つまみの最小サイズは 8px なので // 単位可動域 0-90 を 0-42 ピクセルにマッピングし直して描画する。 // @@ -159,20 +208,22 @@ WXScrollBar::RefreshThumb() // つまみが小さくなりすぎるのを防ぐ int thumbsize_px = thumbsize * range_px / range; - if (thumbsize_px < THUMB_MIN) { - thumbsize_px = THUMB_MIN; + if (thumbsize_px < thumbmin) { + thumbsize_px = thumbmin; } // range はスクロールバー領域全体の長さ (図の 100)。 // mrange はつまみの大きさを含まない可動域?の長さ (図の 90) とする。 // [p1, p2) がつまみのピクセル座標。 + int oldp1 = p1; + int oldp2 = p2; int mrange = range - thumbsize; if (mrange == 0) { // つまみが全体を占めていると可動域 mrange が 0 になる。 p1 = 0; } else { int mrange_px = range_px - thumbsize_px; - p1 = thumbpos * mrange_px / mrange; + p1 = pos * mrange_px / mrange; } p2 = p1 + thumbsize_px; @@ -180,20 +231,26 @@ WXScrollBar::RefreshThumb() p1 += BORDER_LT; p2 += BORDER_LT; - DPRINTF("RefreshThumb: p1=%d p2=%d\n", p1, p2); + // 移動していない (表示位置が変わっていない) なら false で帰る。 + // 目一杯を占めていて移動できないつまみをドラッグしてマウス移動 + // しても、つまみが移動しないのだから、スクロールも起こさないため。 + DPRINTF("SetThumbPosition: oldpos=%d newpos=%d oldp=%d-%d p=%d-%d\n", + thumbpos, pos, oldp1, oldp2, p1, p2); + if (p1 == oldp1 && p2 == oldp2) { + return false; + } + thumbpos = pos; + DPRINTF("SetThumbPosition: new pos=%d\n", thumbpos); Refresh(); + return true; } // つまみの位置を pos に設定し、移動すれば通知イベントを送出する。(内部用) void WXScrollBar::MoveThumb(int pos) { - int old = thumbpos; - - SetThumbPosition(pos); - - if (thumbpos != old) { + if (SetThumbPosition(pos)) { // Orientation は wx"SB_"VERTICAL ではなく wxVERTICAL のほう。 // (値は同じっぽいからいいけど、どうしてこうなった…) int orient = is_vertical ? wxVERTICAL : wxHORIZONTAL; @@ -203,34 +260,39 @@ WXScrollBar::MoveThumb(int pos) } } -// サイズイベント void WXScrollBar::OnSize(wxSizeEvent& event) { - auto s __unused = event.GetSize(); - DPRINTF("OnSize %d,%d\n", s.x, s.y); - - // 親クラス - inherited::OnSize(event); + const wxSize& size = GetSize(); + DPRINTF("%s size=(%d,%d)\n", __method__, size.x, size.y); + // スクロールバーの論理パラメータは変わらず、描画情報が変わる。 if (is_vertical) { - range_px = bitmap.GetHeight() - BORDER_LT - BORDER_RB; + range_px = size.y - BORDER_LT - BORDER_RB; } else { - range_px = bitmap.GetWidth() - BORDER_LT - BORDER_RB; + range_px = size.x - BORDER_LT - BORDER_RB; } - // range_px が変わったので再描画 - RefreshThumb(); + SetThumbPosition(thumbpos); + + event.Skip(); } // マウスイベント void WXScrollBar::OnMouse(wxMouseEvent& event) { - wxPoint pos = event.GetPosition(); - event.Skip(); + // 現在位置 + wxPoint pos = event.GetPosition(); + if (is_vertical) { + mouse_px = pos.y; + } else { + mouse_px = pos.x; + } + DPRINTF("OnMouse: mouse_px=%d\n", mouse_px); + int wheel = event.GetWheelRotation(); if (wheel != 0) { DPRINTF("OnMouse: wheel=%d\n", wheel); @@ -241,37 +303,37 @@ WXScrollBar::OnMouse(wxMouseEvent& event MoveThumb(thumbpos - wheel / 40); } + if (event.LeftUp()) { + left_click = false; + } + if (event.LeftDown() || event.LeftDClick()) { + left_click = true; + // 左クリック。間隔が速いとダブルクリックになる。 // ダブルクリックは LeftDown, LeftUp, LeftDClick, LeftUp の順で来る // ので、LeftDown と LeftDClick を同列で扱っておく。 - int r = PosCmp(pos); + int r = PosCmp(); if (r == 0) { // つまみ内左クリックでは何も起きないが、 // ドラッグ用に左クリック開始時点の位置を保存。 start_thumb = thumbpos; - if (is_vertical) { - start_px = pos.y; - } else { - start_px = pos.x; - } - DPRINTF("OnMouse: LeftDown start_pos=%d px=%d\n", + start_px = mouse_px; + DPRINTF("OnMouse: LeftDown thumbpos=%d start_px=%d\n", start_thumb, start_px); } else { // つまみ外の下地部分ならページアップ、ページダウン DPRINTF("OnMouse: LeftDown cmp=%d\n", r); MoveThumb(thumbpos + r * pagesize); } + + // 押しっぱなし判定のためタイマーを開始。 + timer.Start(500); } if (event.Dragging()) { // ドラッグ開始時からの差分を求めて.. - int delta; - if (is_vertical) { - delta = pos.y - start_px; - } else { - delta = pos.x - start_px; - } + int delta = mouse_px - start_px; DPRINTF("OnMouse: Dragging delta=%d\n", delta); // ピクセルでの移動量を変換 @@ -279,22 +341,31 @@ WXScrollBar::OnMouse(wxMouseEvent& event } } -// pt がつまみ内なら 0 を、つまみより上(左)なら -1、下(右)なら 1 を返す。 -int -WXScrollBar::PosCmp(const wxPoint& pt) const +// タイマーイベント +void +WXScrollBar::OnTimer(wxTimerEvent& event) { - int p; + int r = PosCmp(); + if (left_click && r != 0) { + // つまみ外で左クリックが続いていたらもう1回動かす + MoveThumb(thumbpos + r * pagesize); - if (is_vertical) { - p = pt.y; + // キーリピートと同じ要領で2回目以降は短くする。 + timer.Start(100); } else { - p = pt.x; + // つまみ内か左クリックが終わっていたら終了 + timer.Stop(); } +} - if (p < p1) { +// mouse_px がつまみ内なら 0 を、つまみより上(左)なら -1、下(右)なら 1 を返す。 +int +WXScrollBar::PosCmp() const +{ + if (mouse_px < p1) { return -1; } - if (p >= p2) { + if (mouse_px > p2) { return 1; } return 0; @@ -304,8 +375,16 @@ WXScrollBar::PosCmp(const wxPoint& pt) c void WXScrollBar::Draw() { - const int& w = bitmap.GetWidth(); - const int& h = bitmap.GetHeight(); + // パラメータセット前に表示してしまうとここに来る。 + if (__predict_false(range < 1)) { + return; + } + + const int w = bitmap.GetWidth(); + const int h = bitmap.GetHeight(); + if (__predict_false(w < 4 || h < 4)) { + return; + } if (is_vertical) { // 背景 @@ -315,7 +394,7 @@ WXScrollBar::Draw() bitmap.FillRect(UD_GREY, 0, 1, w - 1, h - 2); // つまみ - bitmap.FillRect(BGPANEL, 1, p1, w - 4, p2 - p1 - 1); + bitmap.FillRect(BGPANEL, 1, p1, w - 3, p2 - p1 - 1); bitmap.DrawLineH(UD_BLACK, 1, p2 - 1, w - 1); bitmap.DrawLineV(UD_BLACK, w - 2, p1, p2); } else { @@ -326,7 +405,7 @@ WXScrollBar::Draw() bitmap.FillRect(UD_GREY, 1, 0, w - 2, h - 1); // つまみ - bitmap.FillRect(BGPANEL, p1, 1, p2 - p1 - 1, h - 4); + bitmap.FillRect(BGPANEL, p1, 1, p2 - p1 - 1, h - 3); bitmap.DrawLineV(UD_BLACK, p2 - 1, 1, h - 1); bitmap.DrawLineH(UD_BLACK, p1, h - 2, p2); }