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

scroll mode 📜 #53

Merged
merged 14 commits into from
Jun 28, 2024
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