Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement of scroll snap method to support vertical and horizontal snap #555

Closed

Conversation

nebula121
Copy link

This pull request is an improvement of the scroll snap method.
The goal is to support vertical scroll snap and horizontal scroll snap at the same time.

Pull requestを英語で書いてから日本語でも良かったことに気づきました…。
C言語のコードを書くのもPull requestを送るのも初めてです。
お作法間違いがございましたらご指摘いただけるとありがたいです。

私の環境ではこの機能修正でfirmwareが74byte増加しました。
過去にスクロールスナップ機能周りは諸々検討済みと伺っておりますが、
もしそれらと方向性が重複していなければご検討いただけると幸いです。

Improved Method

Initially, the scroll_snap_mode is 0.
In this mode == 0, any direction scrolling is allowed.

Then the trackball inputs a value r->v >= KEYBALL_SCROLLSNAP_TENSION_THRESHOLD_1ST, the scroll_snap_mode changes from 0 to 1.
In this mode == 1, only vertical scrolling is allowed.
Similarly, the trackball inputs a value r->h >= KEYBALL_SCROLLSNAP_TENSION_THRESHOLD_1ST, the scroll_snap_mode changes from 0 to 2.
In this mode == 2, only horizontal scrolling is allowed.
In addition, the trackball inputs values r->v >= ..._1ST and r->h >= ..._1ST at the same time, the scroll_snap_mode changes from 0 to 3.
In this mode == 3, any direction scrolling is allowed.

When the mode is 1, the trackball inputs a value r->h >= KEYBALL_SCROLLSNAP_TENSION_THRESHOLD_2ND, the scroll_snap_mode changes from 1 to 3.
Similarly, when the mode is 2, the trackball inputs a value r->v >= ..._2ND, the scroll_snap_mode changes from 2 to 3.

When the mode is 1 / 2 / 3, the trackball inputs nothing and time has passed TIMER_DIFF_32(now, keyball.prev_scroll_time) >= KEYBALL_SCROLLSNAP_RESET_TIMER, the scroll_snap_mode changes from 1 / 2 / 3 to 0.

Flow Chart

🤔🤔🤔

graph LR
    subgraph "Idle mode (Default)"
        A[mode = 0]
    end

    subgraph Vertical scroll mode
        B[mode = 1] --> N[r->h  = 0]
    end

    subgraph Horizontal scroll mode
        C[mode = 2] --> M[r->v = 0]
    end

    subgraph Free scroll mode
        D[mode = 3]
    end

    A -->|Vertical move| B
    A -->|Horizontal move| C
    A -->|Vertical and horizontal move| D
    B -->|Horizontal move| D
    C -->|Vertical move| D
    B -->|Vertical move| B
    B & C & D -->|Without move and time past| A
    C -->|Horizontal move| C
    D -->|Any move| D
Loading

@koron
Copy link
Collaborator

koron commented Apr 15, 2024

水平方向にもスナップできるようにしたいのだと理解しました。

しかしそれに近い方式は過去に試しており下記のような理由で棄却しています。

引用元: https://github.com/Yowkees/keyball/blob/main/qmk_firmware/keyboards/keyball/lib/keyball/README.md#history-of-scroll-snap

最初のスナップ機能は垂直・水平のどちらかの軸から一定角度以内で収まってるうちはそちらへスナップするとした。 しかし回転開始初期にはその移動量が極小かつセンサーの感度が高いので、 垂直に動かしたい時に水平にも極小量の移動が発生しておりスナップ方向が定まらない、 という問題が発生した。 人間は自分が思うほどには指を正確に動かせていなかった。

つまりこのPRでも過去と同様、大半の人がいきなりmode = 3(自由スクロール)になってしまい、スナップしていることを実感できないと考えられます。
また仮に KEYBALL_SCROLLSNAP_TENSION_THRESHOLD_1ST を大きくしても、それは動き出しの鈍さに繋がることがわかってます。

そこで一定方向に一定以上のカウントを検出するまでは一切スクロールしないようにした。 これは回転開始初期のスクロール量を読み捨てるに等しい。
(上記URLから続きを引用)

またモード遷移が非常に複雑なため、大半の人にとっては体感として理解できない恐れが非常に強いです。
モーダルな操作というのは人間にとって直感的ではないため、可能な限り避けるかシンプルにすべきです。

以上のことから、標準機能としてこのパッチを取り込むことは非常に難しいと言わざるをえません。
ただ自分でコンパイルする人が有効化できるオプション機能としては再考の余地がありそうです。
それはまた別に過去の実装と併せて検討したほうがよいでしょう。

あと本題とは関係ありませんが、添付していただいた図は「Flow Chart」ではなく
一般的には「状態遷移図 (State Transition Diagram)」と言われるものです。

@koron
Copy link
Collaborator

koron commented Apr 15, 2024

参考:

static void motion_to_mouse_scroll(keyball_motion_t *m, report_mouse_t *r, bool is_left) {
// consume motion of trackball.
uint8_t div = keyball_get_scroll_div() - 1;
int16_t x = m->x >> div;
m->x -= x << div;
int16_t y = m->y >> div;
m->y -= y << div;
#if KEYBALL_SCROLLSNAP_ENABLE
// scroll snap.
uint32_t now = timer_read32();
if (x != 0 || y != 0) {
keyball.scroll_snap_last = now;
} else if (TIMER_DIFF_32(now, keyball.scroll_snap_last) >= KEYBALL_SCROLLSNAP_RESET_TIMER) {
keyball.scroll_snap_tension.x = 0;
keyball.scroll_snap_tension.y = 0;
}
if (abs16(keyball.scroll_snap_tension.x) < KEYBALL_SCROLLSNAP_TENSION_THRESHOLD) {
keyball.scroll_snap_tension.x += x;
x = 0;
}
if (abs16(keyball.scroll_snap_tension.y) < KEYBALL_SCROLLSNAP_TENSION_THRESHOLD) {
keyball.scroll_snap_tension.y += y;
y = 0;
}
#endif
// apply to mouse report.
#if KEYBALL_MODEL == 61 || KEYBALL_MODEL == 39
r->h = clip2int8(y);
r->v = -clip2int8(x);
if (is_left) {
r->h = -r->h;
r->v = -r->v;
}
#elif KEYBALL_MODEL == 46
r->h = clip2int8(x);
r->v = clip2int8(y);
#else
# error("unknown Keyball model")
#endif
}

以下に相当するコード

そこで一定方向に一定以上のカウントを検出するまでは一切スクロールしないようにした。 これは回転開始初期のスクロール量を読み捨てるに等しい。

@koron koron mentioned this pull request Apr 15, 2024
@koron
Copy link
Collaborator

koron commented Apr 15, 2024

検討しましたがこの方式では受け入れられないとの結論になりました。
ご希望に添えなくてごめんなさい。

代わりと言っては何ですが、
スクロールスナップをカスタマイズできる方法を #559 で考え始めました。
ざっくりいうと
スナップモードをキーで切り替える&キーマップごとに自前で実装できる
という建て付けになっています。

よろしければそちらの方にご意見ください。

@koron koron closed this Apr 15, 2024
@nebula121
Copy link
Author

nebula121 commented Apr 15, 2024

@koron
拙いPull Requestにも関わらずご確認、ご返信ありがとうございます。
あああ…これはフローチャートではなく状態遷移図なんですね…。お恥ずかしい限りです(笑)

ご指摘いただいた点、私の意図と異なる部分がございましたので、Close後ではありますがご返信させてください。


順番前後しますが、、

モード遷移が非常に複雑なため、大半の人にとっては体感として理解できないのではないか

分かりづらい状態遷移図を載せたのがダメでしたね…。
ユーザーが意図する操作は下記の2ルートのみで、概ね現行方式に水平が加わっただけのつもりです。
私個人としては、実際に触っても直感に反するような事はありませんでした。

  • mode0=自由 → mode1=鉛直 (→ mode3=自由) → mode0=元に戻る
  • mode0=自由 → mode2=水平 (→ mode3=自由) → mode0=元に戻る

いきなりmode = 3(自由スクロール)になってしまうのではないか

KEYBALL_SCROLLSNAP_TENSION_THRESHOLD_1ST を大きくすると動き出しの鈍さに繋がるのではないか

koron様がご検討されていた内容は「スクロール方向を見定めるまで操作を読み捨てる必要がある」と「読み捨てるとユーザビリティを損なう」のトレードオフの中で、「ユーザビリティの許容範囲内の操作量は微小すぎて方向が定まらない」と結論付けられたと理解しています。

koron様と私の方式を比べると下記と認識していますが、合っていますでしょうか?

動き出し 評価値 基準値(閾値)
koron様方式 読み捨て 各方向の移動総量 「初期→鉛直or水平」と「鉛直or水平→自由」で共通
私方式 自由スクロール 各方向の実行周期ごと移動量 「初期→鉛直or水平」と「鉛直or水平→自由」で別

この元、ユーザーの操作に対する初期→鉛直スク→自由スクの遷移は下記のようになると思っています。

鉛直スクロールへの遷移タイミング 自由スクロールの遷移タイミング
koron様方式 小さな閾値の超過(鉛直方向)
閾値大きくすると動き出し鈍くなる
小さな閾値の超過(水平方向)
積算なので斜めにスクロールするといつか遷移する
私方式 小さな閾値の超過(鉛直方向)
閾値大きくすると自由スクロールする
大きな閾値の超過(水平方向)
周期ごと判定なので斜めにスクロールしても遷移しない

したがってご返答としては「軽い操作で意図せずmode = 3(自由スクロール)にはならない」、「動き出しは自由スクロールのためユーザビリティは損なわない」となります。

あわよくばご再考の余地があれば…とご返信しましたがご確認いただけるだけでもありがたいです。よろしくお願い致します。

ちなみになんでかよくわかってないのですが、むしろ私方式でmode0→mode3にいきなり遷移させる方法がわかってないです…(scroll_divの設定値が大きいからなのか…)

@koron
Copy link
Collaborator

koron commented Apr 15, 2024

デフォルトが自由スクロールなのはもっとよろしくないのです。
多くのユーザーにとっては垂直スクロールが最も頻繁に使う操作となります。
にもかかわらず不意に横成分を含んだスクロールが発生すると混乱します。

いずれにせよボールの回転量を元に状態遷移をする方式は
ユーザーの不随意運動により意図してない動作をする可能性があるので好ましくない
という考えに基づいて進めていくつもりです。

@nebula121
Copy link
Author

ご丁寧にありがとうございます。
不意な横成分スクロールでユーザビリティを損なうことが懸念とのこと理解しました。
これを防ごうと思うと無段階スクロールの場合はどうにもならなさそうですね。
(無段階スクロール or 段階スクロールがハード側の制御なのかアプリ側の制御なのかわかっていませんが…)

なにはともあれマージされなかった事情は納得です。
個人の用途範囲では満足なので引き続きfirmwareの面でも楽しみながら使っていこうと思います。
ありがとうございました。大変勉強になりました。

@nebula121 nebula121 deleted the feature/improve_scroll_snap_method branch April 18, 2024 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants