Skip to content

Commit

Permalink
scroll mode 📜 (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
eagleoflqj authored Jun 28, 2024
1 parent 5a01930 commit a8aa4d5
Show file tree
Hide file tree
Showing 12 changed files with 567 additions and 66 deletions.
53 changes: 46 additions & 7 deletions include/candidate_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ enum theme_t { system = 0, light = 1, dark = 2 };

enum writing_mode_t { horizontal_tb = 0, vertical_rl = 1, vertical_lr = 2 };

enum scroll_state_t { none = 0, ready = 1, scrolling = 2 };

enum scroll_key_action_t {
one = 1,
two = 2,
three = 3,
four = 4,
five = 5,
six = 6,
up = 10,
down = 11,
left = 12,
right = 13,
home = 14,
end = 15,
page_up = 16,
page_down = 17,
expand = 18,
collapse = 19,
commit = 20
};

struct CandidateAction {
int id;
std::string text;
Expand All @@ -47,8 +69,11 @@ class CandidateWindow {
const formatted<std::string> &auxUp,
const formatted<std::string> &auxDown) = 0;
virtual void set_candidates(const std::vector<Candidate> &candidates,
int highlighted) = 0;
virtual void set_highlight_callback(std::function<void(size_t index)>) = 0;
int highlighted, scroll_state_t scroll_state,
bool scroll_start, bool scroll_end) = 0;
virtual void scroll_key_action(scroll_key_action_t action) = 0;
virtual void
answer_actions(const std::vector<CandidateAction> &actions) = 0;
virtual void set_theme(theme_t theme) = 0;
virtual void set_writing_mode(writing_mode_t mode) = 0;
virtual void set_style(const void *style) = 0;
Expand All @@ -59,10 +84,14 @@ class CandidateWindow {
init_callback = callback;
}

void set_select_callback(std::function<void(size_t index)> callback) {
void set_select_callback(std::function<void(int index)> callback) {
select_callback = callback;
}

void set_highlight_callback(std::function<void(int index)> callback) {
highlight_callback = callback;
}

void set_cursor_text(const std::string &text) { cursor_text_ = text; }
void set_highlight_mark_text(const std::string &text) {
highlight_mark_text_ = text;
Expand All @@ -72,22 +101,32 @@ class CandidateWindow {
page_callback = callback;
}

void set_scroll_callback(std::function<void(int, int)> callback) {
scroll_callback = callback;
}

void set_paging_buttons(bool pageable, bool has_prev, bool has_next) {
pageable_ = pageable;
has_prev_ = has_prev;
has_next_ = has_next;
}

void
set_action_callback(std::function<void(size_t index, int id)> callback) {
void set_ask_actions_callback(std::function<void(int index)> callback) {
ask_actions_callback = callback;
}

void set_action_callback(std::function<void(int index, int id)> callback) {
action_callback = callback;
}

protected:
std::function<void()> init_callback = []() {};
std::function<void(size_t index)> select_callback = [](size_t) {};
std::function<void(int index)> select_callback = [](int) {};
std::function<void(int index)> highlight_callback = [](int) {};
std::function<void(bool next)> page_callback = [](bool) {};
std::function<void(size_t index, int id)> action_callback = [](int, int) {};
std::function<void(int, int)> scroll_callback = [](int, int) {};
std::function<void(int index)> ask_actions_callback = [](int) {};
std::function<void(int index, int id)> action_callback = [](int, int) {};
std::string cursor_text_ = "";
std::string highlight_mark_text_ = "";
bool pageable_ = false;
Expand Down
6 changes: 4 additions & 2 deletions include/webview_candidate_window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ class WebviewCandidateWindow : public CandidateWindow {
const formatted<std::string> &auxUp,
const formatted<std::string> &auxDown) override;
void set_candidates(const std::vector<Candidate> &candidates,
int highlighted) override;
void set_highlight_callback(std::function<void(size_t index)>) override {}
int highlighted, scroll_state_t scroll_state,
bool scroll_start, bool scroll_end) override;
void scroll_key_action(scroll_key_action_t action) override;
void answer_actions(const std::vector<CandidateAction> &actions) override;
void set_theme(theme_t theme) override;
void set_writing_mode(writing_mode_t mode) override;
void set_style(const void *style) override;
Expand Down
61 changes: 52 additions & 9 deletions page/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
div,
setActions,
answerActions,
hideContextmenu,
getHoverBehavior,
getPagingButtonsStyle,
Expand All @@ -19,6 +20,14 @@ import {
} from './theme'
import { setStyle } from './customize'
import { fcitxLog } from './log'
import {
getScrollState,
setScrollState,
setScrollEnd,
recalculateScroll,
scrollKeyAction,
fetchComplete
} from './scroll'

window.fcitxLog = fcitxLog
window._onload && window._onload()
Expand Down Expand Up @@ -83,19 +92,30 @@ const caretRight = common.replace('{}', '0 0 192 512').replace('{}', 'M0 384.662
const arrowBack = common.replace('{}', '0 0 24 24').replace('{}', 'M16.62 2.99a1.25 1.25 0 0 0-1.77 0L6.54 11.3a.996.996 0 0 0 0 1.41l8.31 8.31c.49.49 1.28.49 1.77 0s.49-1.28 0-1.77L9.38 12l7.25-7.25c.48-.48.48-1.28-.01-1.76z')
const arrowForward = common.replace('{}', '0 0 24 24').replace('{}', 'M7.38 21.01c.49.49 1.28.49 1.77 0l8.31-8.31a.996.996 0 0 0 0-1.41L9.15 2.98c-.49-.49-1.28-.49-1.77 0s-.49 1.28 0 1.77L14.62 12l-7.25 7.25c-.48.48-.48 1.28.01 1.76z')

function setCandidates (cands: Candidate[], highlighted: number, markText: string, pageable: boolean, hasPrev: boolean, hasNext: boolean) {
hoverables.innerHTML = ''
function setCandidates (cands: Candidate[], highlighted: number, markText: string, pageable: boolean, hasPrev: boolean, hasNext: boolean, scrollState: SCROLL_STATE, scrollStart: boolean, scrollEnd: boolean) {
setScrollState(scrollState)
// Clear existing candidates when scroll continues.
if (scrollState !== 2 || scrollStart) {
hoverables.innerHTML = ''
hoverables.scrollTop = 0 // Otherwise last scroll position will be kept.
} else {
fetchComplete()
}
if (scrollState === 2) {
hoverables.classList.add('horizontal-scroll')
setScrollEnd(scrollEnd)
} else {
hoverables.classList.remove('horizontal-scroll')
}
for (let i = 0; i < cands.length; ++i) {
const candidate = div('candidate', 'hoverable')
if (i === 0) {
if (i === 0 && scrollState !== 2) {
candidate.classList.add('candidate-first')
} else {
hoverables.append(divider())
}
if (i === highlighted) {
candidate.classList.add('highlighted', 'highlighted-original')
}
if (i === cands.length - 1) {
if (i === cands.length - 1 && scrollState !== 2) {
candidate.classList.add('candidate-last')
}

Expand All @@ -112,9 +132,9 @@ function setCandidates (cands: Candidate[], highlighted: number, markText: strin
candidateInner.append(mark)
}

if (cands[i].label) {
if (cands[i].label || scrollState === 2) {
const label = div('label')
label.innerHTML = escapeWS(cands[i].label)
label.innerHTML = escapeWS(cands[i].label || '0')
candidateInner.append(label)
}

Expand All @@ -130,11 +150,25 @@ function setCandidates (cands: Candidate[], highlighted: number, markText: strin

candidate.append(candidateInner)
hoverables.append(candidate)

// No divider after last element in non-scroll mode,
// but for scroll mode it needs to fill the row when
// candidates are not enough.
if (scrollState === 2 || i !== cands.length - 1) {
hoverables.append(divider())
}
}

setActions(cands.map(c => c.actions))

if (pageable) {
if (scrollState === 1) {
hoverables.append(divider(true))
const expand = div('expand', 'hoverable-inner')
expand.innerHTML = arrowForward
const paging = div('paging', 'scroll', 'hoverable')
paging.append(expand)
hoverables.append(paging)
} else if (scrollState === 0 && pageable) {
const isArrow = getPagingButtonsStyle() === 'Arrow'
hoverables.append(divider(true))

Expand Down Expand Up @@ -163,6 +197,10 @@ function setCandidates (cands: Candidate[], highlighted: number, markText: strin
paging.appendChild(prev)
paging.appendChild(next)
hoverables.appendChild(paging)
} else if (scrollState === 2) {
window.requestAnimationFrame(() => {
recalculateScroll(scrollStart)
})
}

for (const hoverable of hoverables.querySelectorAll('.hoverable')) {
Expand Down Expand Up @@ -207,6 +245,9 @@ hoverables.addEventListener('mouseleave', () => {
})

hoverables.addEventListener('wheel', e => {
if (getScrollState() === 2) {
return
}
window._page((<WheelEvent>e).deltaY > 0)
})

Expand All @@ -222,3 +263,5 @@ window.setAccentColor = setAccentColor
window.setStyle = setStyle
window.setWritingMode = setWritingMode
window.copyHTML = copyHTML
window.scrollKeyAction = scrollKeyAction
window.answerActions = answerActions
12 changes: 12 additions & 0 deletions page/customize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ const HEADER_LIGHT_BACKGROUND = `${PANEL_LIGHT} .header`
const HOVERABLES_LIGHT_BACKGROUND = `${PANEL_LIGHT} .hoverables :is(.candidate, .paging)`
const PANEL_LIGHT_DIVIDER_MIDDLE = `${PANEL_LIGHT} .hoverables .divider .divider-middle`
const PANEL_LIGHT_DIVIDER_SIDE = `${PANEL_LIGHT} .hoverables .divider .divider-side`
const PANEL_LIGHT_SCROLL_DIVIDER = `${PANEL_LIGHT} .hoverables.horizontal-scroll .divider-middle`
const PANEL_LIGHT_SCROLL_TRACK = `${PANEL_LIGHT} .hoverables.horizontal-scroll::-webkit-scrollbar-track`
const CURSOR_NO_TEXT_LIGHT = `${PANEL_LIGHT} .cursor.no-text`
const HIGHLIGHT_MARK_LIGHT = `${PANEL_LIGHT} .highlighted .mark`

Expand All @@ -148,6 +150,8 @@ const HEADER_DARK_BACKGROUND = lightToDark(HEADER_LIGHT_BACKGROUND)
const HOVERABLES_DARK_BACKGROUND = lightToDark(HOVERABLES_LIGHT_BACKGROUND)
const PANEL_DARK_DIVIDER_MIDDLE = lightToDark(PANEL_LIGHT_DIVIDER_MIDDLE)
const PANEL_DARK_DIVIDER_SIDE = lightToDark(PANEL_LIGHT_DIVIDER_SIDE)
const PANEL_DARK_SCROLL_DIVIDER = lightToDark(PANEL_LIGHT_SCROLL_DIVIDER)
const PANEL_DARK_SCROLL_TRACK = lightToDark(PANEL_LIGHT_SCROLL_TRACK)
const CURSOR_NO_TEXT_DARK = lightToDark(CURSOR_NO_TEXT_LIGHT)
const HIGHLIGHT_MARK_DARK = lightToDark(HIGHLIGHT_MARK_LIGHT)

Expand Down Expand Up @@ -241,6 +245,9 @@ export function setStyle (style: string) {
rules[PANEL_LIGHT_DIVIDER_SIDE] = {
'background-color': lightBackgroundColor
}
rules[PANEL_LIGHT_SCROLL_DIVIDER] = rules[PANEL_LIGHT_SCROLL_TRACK] = {
'background-color': lightBackgroundColor
}
rules[HIGHLIGHT_MARK_LIGHT] = {
[markKey]: j.LightMode.HighlightMarkColor
}
Expand Down Expand Up @@ -276,6 +283,8 @@ export function setStyle (style: string) {
PANEL_LIGHT,
PANEL_LIGHT_DIVIDER_MIDDLE,
PANEL_LIGHT_DIVIDER_SIDE,
PANEL_LIGHT_SCROLL_DIVIDER,
PANEL_LIGHT_SCROLL_TRACK,
HIGHLIGHT_MARK_LIGHT
]
if (j.Highlight.HoverBehavior === 'Add') {
Expand Down Expand Up @@ -345,6 +354,9 @@ export function setStyle (style: string) {
rules[PANEL_DARK_DIVIDER_SIDE] = {
'background-color': darkBackgroundColor
}
rules[PANEL_DARK_SCROLL_DIVIDER] = rules[PANEL_DARK_SCROLL_TRACK] = {
'background-color': darkBackgroundColor
}
rules[HIGHLIGHT_MARK_DARK] = {
[markKey]: j.DarkMode.HighlightMarkColor
}
Expand Down
81 changes: 62 additions & 19 deletions page/generic.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ body {
}
}

.candidate-inner {
display: flex;
gap: 6px;
align-items: center; /* English words have lower height */
line-height: 1em; /* align label and candidates */
position: relative; /* for absolute position of mark */
}

.label {
/* Label is usually a single number. Will look ugly when all parts have vertical writing mode. */
writing-mode: horizontal-tb;
}

.hoverables {
display: flex;

Expand All @@ -50,30 +63,49 @@ body {
&.horizontal {
flex-direction: row;

.candidate {
/* When horizontal and there is multi-line candidate,
make sure other candidates are vertical centered.
Don't enable it for vertical. It will shrink highlight. */
display: flex;
}

.divider {
flex-direction: column;
}
}
}

.horizontal .candidate {
/* When horizontal and there is multi-line candidate,
make sure other candidates are vertical centered.
Don't enable it for vertical. It will shrink highlight. */
display: flex;
}
&.horizontal-scroll {
max-block-size: 180px; /* If block-size, 2 rows will have 90px each. */
inline-size: 400px;
flex-wrap: wrap;
overflow-y: auto;
overscroll-behavior: none;

.candidate-inner {
display: flex;
gap: 6px;
align-items: center; /* English words have lower height */
line-height: 1em; /* align label and candidates */
position: relative; /* for absolute position of mark */
.candidate {
min-inline-size: 60px;
}

.candidate-inner {
width: 100%;
}

.label {
opacity: 0;
}

.highlighted-row .label {
opacity: 1;
}

.divider {
flex-grow: 1;
}
}
}

.label {
/* Label is usually a single number. Will look ugly when all parts have vertical writing mode. */
writing-mode: horizontal-tb;
:is(.vertical-rl, .vertical-lr) .paging svg {
transform: rotate(90deg);
}

.paging {
Expand All @@ -88,10 +120,21 @@ body {
block-size: 16px;
inline-size: 16px;
}
}

:is(.vertical-rl, .vertical-lr) .paging svg {
transform: rotate(90deg);
&.scroll {
.expand {
block-size: 18px;
inline-size: 18px;
display: flex;
justify-content: center;
align-items: center;

svg {
transform: rotate(90deg);
width: 16px;
}
}
}
}

/* When horizontal, paging is shorter than candidates, so need to centralize them. */
Expand Down
Loading

0 comments on commit a8aa4d5

Please sign in to comment.