feat: dividers in word processor, login screen improvements

This commit is contained in:
2026-04-02 16:41:34 +02:00
parent 8bbe31e390
commit ee667e917b
9 changed files with 574 additions and 92 deletions
+40 -4
View File
@@ -41,6 +41,41 @@
namespace gui {
inline int decode_single_byte_codepoint(int codepoint) {
if (codepoint < 0 || codepoint > 255) return codepoint;
switch (codepoint) {
case 0x80: return 0x20AC; // Euro Sign
case 0x82: return 0x201A; // Single Low-9 Quotation Mark
case 0x83: return 0x0192; // Latin Small Letter F With Hook
case 0x84: return 0x201E; // Double Low-9 Quotation Mark
case 0x85: return 0x2026; // Horizontal Ellipsis
case 0x86: return 0x2020; // Dagger
case 0x87: return 0x2021; // Double Dagger
case 0x88: return 0x02C6; // Modifier Letter Circumflex Accent
case 0x89: return 0x2030; // Per Mille Sign
case 0x8A: return 0x0160; // Latin Capital Letter S With Caron
case 0x8B: return 0x2039; // Single Left-Pointing Angle Quotation Mark
case 0x8C: return 0x0152; // Latin Capital Ligature OE
case 0x8E: return 0x017D; // Latin Capital Letter Z With Caron
case 0x91: return 0x2018; // Left Single Quotation Mark
case 0x92: return 0x2019; // Right Single Quotation Mark
case 0x93: return 0x201C; // Left Double Quotation Mark
case 0x94: return 0x201D; // Right Double Quotation Mark
case 0x95: return 0x2022; // Bullet
case 0x96: return 0x2013; // En Dash
case 0x97: return 0x2014; // Em Dash
case 0x98: return 0x02DC; // Small Tilde
case 0x99: return 0x2122; // Trade Mark Sign
case 0x9A: return 0x0161; // Latin Small Letter S With Caron
case 0x9B: return 0x203A; // Single Right-Pointing Angle Quotation Mark
case 0x9C: return 0x0153; // Latin Small Ligature OE
case 0x9E: return 0x017E; // Latin Small Letter Z With Caron
case 0x9F: return 0x0178; // Latin Capital Letter Y With Diaeresis
default: return codepoint;
}
}
struct CachedGlyph {
uint8_t* bitmap;
int width, height;
@@ -50,7 +85,7 @@ struct CachedGlyph {
};
struct GlyphCache {
CachedGlyph glyphs[256]; // Latin-1 Supplement (U+0080..U+00FF) included
CachedGlyph glyphs[256]; // Single-byte text with Windows-1252 punctuation mapping
int pixel_size;
float scale;
int ascent, descent, line_gap;
@@ -155,12 +190,13 @@ struct TrueTypeFont {
if (g->loaded) return g;
g->loaded = true;
int font_codepoint = decode_single_byte_codepoint(codepoint);
int advance, lsb;
stbtt_GetCodepointHMetrics(&info, codepoint, &advance, &lsb);
stbtt_GetCodepointHMetrics(&info, font_codepoint, &advance, &lsb);
g->advance = (int)(advance * gc->scale);
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&info, codepoint, gc->scale, gc->scale,
stbtt_GetCodepointBitmapBox(&info, font_codepoint, gc->scale, gc->scale,
&x0, &y0, &x1, &y1);
g->width = x1 - x0;
g->height = y1 - y0;
@@ -172,7 +208,7 @@ struct TrueTypeFont {
if (!g->bitmap)
return g;
stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height,
g->width, gc->scale, gc->scale, codepoint);
g->width, gc->scale, gc->scale, font_codepoint);
}
return g;
+18 -7
View File
@@ -10,6 +10,23 @@ using namespace gui;
namespace {
void draw_login_background(LoginState* ls) {
Framebuffer& fb = ls->fb;
if (!ls->has_wallpaper) {
fb.clear(BG_COLOR);
return;
}
uint32_t* dst = fb.buffer();
int pitch = fb.pitch();
int row_bytes = ls->screen_w * (int)sizeof(uint32_t);
for (int y = 0; y < ls->screen_h; y++) {
uint32_t* dst_row = (uint32_t*)((uint8_t*)dst + y * pitch);
const uint32_t* src_row = ls->bg_wallpaper + y * ls->bg_wallpaper_w;
montauk::memcpy(dst_row, src_row, row_bytes);
}
}
Rect offset_rect(Rect rect, int dx, int dy) {
rect.x += dx;
rect.y += dy;
@@ -295,13 +312,7 @@ void draw_login_screen(LoginState* ls) {
LoginLayout lo = layout_login_screen(ls);
int sfh = system_font_height();
if (ls->has_wallpaper) {
fb.blit(0, 0, ls->bg_wallpaper_w, ls->bg_wallpaper_h, ls->bg_wallpaper);
fb.fill_rect_alpha(0, 0, ls->screen_w, ls->screen_h,
Color::from_rgba(0, 0, 0, 0x38));
} else {
fb.clear(BG_COLOR);
}
draw_login_background(ls);
draw_shadow(fb, lo.card.x, lo.card.y, lo.card.w, lo.card.h, 4, colors::SHADOW);
fb.fill_rect(lo.card.x, lo.card.y, lo.card.w, lo.card.h, CARD_BG);
+18 -3
View File
@@ -6,6 +6,18 @@
#include "login.hpp"
namespace {
constexpr uint32_t kLoginOverlayAlpha = 0x38;
constexpr uint32_t kLoginOverlayInvAlpha = 255 - kLoginOverlayAlpha;
static uint8_t dim_component(uint8_t value) {
uint32_t scaled = kLoginOverlayInvAlpha * value;
return (uint8_t)((scaled + 1 + (scaled >> 8)) >> 8);
}
} // namespace
bool load_login_wallpaper(LoginState* ls) {
auto doc = montauk::config::load("desktop");
const char* wp = doc.get_string("wallpaper.path", "");
@@ -69,10 +81,13 @@ bool load_login_wallpaper(LoginState* ls) {
if (sx < 0) sx = 0;
if (sx >= img_w) sx = img_w - 1;
int si = (sy * img_w + sx) * 3;
uint8_t r = dim_component(rgb[si]);
uint8_t g = dim_component(rgb[si + 1]);
uint8_t b = dim_component(rgb[si + 2]);
scaled[y * dst_w + x] = 0xFF000000u
| ((uint32_t)rgb[si] << 16)
| ((uint32_t)rgb[si + 1] << 8)
| (uint32_t)rgb[si + 2];
| ((uint32_t)r << 16)
| ((uint32_t)g << 8)
| (uint32_t)b;
}
}
+3 -1
View File
@@ -70,6 +70,7 @@ extern "C" void _start() {
for (;;) {
bool mouse_changed = false;
bool key_changed = false;
bool fast_mouse_path = false;
int prev_mouse_x = ls->mouse.x;
int prev_mouse_y = ls->mouse.y;
@@ -80,6 +81,7 @@ extern "C" void _start() {
|| ls->mouse.y != prev_mouse_y
|| ls->mouse.buttons != prev_mouse_buttons
|| ls->mouse.scrollDelta != 0;
fast_mouse_path = mouse_changed || ls->mouse.buttons != 0;
while (montauk::is_key_available()) {
Montauk::KeyEvent key;
@@ -93,7 +95,7 @@ extern "C" void _start() {
if (first_frame || mouse_changed || key_changed) {
draw_login_screen(ls);
first_frame = false;
montauk::sleep_ms(4);
montauk::sleep_ms(fast_mouse_path ? 1 : 4);
} else {
montauk::sleep_ms(16);
}
+217 -34
View File
@@ -11,6 +11,10 @@ static inline int wp_min_int(int a, int b) {
return a < b ? a : b;
}
static bool wp_valid_divider_type(uint8_t divider_type) {
return divider_type >= PARA_DIVIDER_SINGLE && divider_type <= PARA_DIVIDER_THICK_THIN;
}
static void wp_init_run(StyledRun* r, uint8_t font_id, uint8_t size, uint8_t flags) {
r->cap = 64;
r->text = (char*)montauk::malloc(r->cap);
@@ -44,18 +48,67 @@ void wp_init_paragraph_style(ParagraphStyle* para) {
para->align = PARA_ALIGN_LEFT;
para->list_type = PARA_LIST_NONE;
para->line_spacing = 100;
para->_pad = 0;
para->divider_type = PARA_DIVIDER_NONE;
para->left_indent = 0;
para->first_line_indent = 0;
para->space_before = 0;
para->space_after = 0;
}
static void wp_font_at(WordProcessorState* wp, int abs_pos,
static ParagraphStyle wp_make_divider_style(const ParagraphStyle* src, uint8_t divider_type) {
ParagraphStyle style = *src;
style.align = PARA_ALIGN_LEFT;
style.list_type = PARA_LIST_NONE;
style.divider_type = divider_type;
style.left_indent = 0;
style.first_line_indent = 0;
style.space_before = 0;
style.space_after = 0;
return style;
}
static void wp_paragraph_bounds(WordProcessorState* wp, int para_idx,
int* out_start, int* out_end, bool* out_has_newline) {
if (out_start) *out_start = 0;
if (out_end) *out_end = 0;
if (out_has_newline) *out_has_newline = false;
if (para_idx < 0) para_idx = 0;
if (para_idx >= wp->paragraph_count) para_idx = wp->paragraph_count - 1;
if (para_idx < 0) return;
int start = 0;
int cur_para = 0;
while (cur_para < para_idx && start < wp->total_text_len) {
while (start < wp->total_text_len && wp_char_at(wp, start) != '\n')
start++;
if (start < wp->total_text_len && wp_char_at(wp, start) == '\n')
start++;
cur_para++;
}
int end = start;
bool has_newline = false;
while (end < wp->total_text_len) {
if (wp_char_at(wp, end) == '\n') {
has_newline = true;
break;
}
end++;
}
if (out_start) *out_start = start;
if (out_end) *out_end = end;
if (out_has_newline) *out_has_newline = has_newline;
}
static void wp_font_at(WordProcessorState* wp, int abs_pos, int layout_dpi,
TrueTypeFont** out_font, GlyphCache** out_gc) {
if (wp->run_count <= 0) {
*out_font = wp_get_font(wp->cur_font_id, wp->cur_flags);
*out_gc = (*out_font && (*out_font)->valid) ? (*out_font)->get_cache(wp->cur_size) : nullptr;
*out_gc = (*out_font && (*out_font)->valid)
? (*out_font)->get_cache(wp_points_to_pixels(wp->cur_size, layout_dpi))
: nullptr;
return;
}
@@ -65,7 +118,9 @@ static void wp_font_at(WordProcessorState* wp, int abs_pos,
if (r < 0) r = 0;
StyledRun* run = &wp->runs[r];
*out_font = wp_get_font(run->font_id, run->flags);
*out_gc = (*out_font && (*out_font)->valid) ? (*out_font)->get_cache(run->size) : nullptr;
*out_gc = (*out_font && (*out_font)->valid)
? (*out_font)->get_cache(wp_points_to_pixels(run->size, layout_dpi))
: nullptr;
}
static void wp_free_runs(WordProcessorState* wp) {
@@ -145,6 +200,7 @@ static void wp_on_insert_newline(WordProcessorState* wp, int abs_pos) {
if (para < 0) para = 0;
if (para >= wp->paragraph_count) para = wp->paragraph_count - 1;
ParagraphStyle style = wp->paragraphs[para];
style.divider_type = PARA_DIVIDER_NONE;
wp_insert_paragraph_style(wp, para + 1, &style);
}
@@ -154,42 +210,43 @@ static void wp_on_delete_newline(WordProcessorState* wp, int abs_pos) {
wp_remove_paragraph_style(wp, para + 1);
}
static void wp_default_line_metrics(WordProcessorState* wp, int abs_pos, int* out_height, int* out_ascent) {
static void wp_default_line_metrics(WordProcessorState* wp, int abs_pos, int layout_dpi,
int* out_height, int* out_ascent) {
TrueTypeFont* font = nullptr;
GlyphCache* gc = nullptr;
if (wp->total_text_len > 0) {
int pos = abs_pos;
if (pos >= wp->total_text_len) pos = wp->total_text_len - 1;
if (pos < 0) pos = 0;
wp_font_at(wp, pos, &font, &gc);
wp_font_at(wp, pos, layout_dpi, &font, &gc);
} else {
font = wp_get_font(wp->cur_font_id, wp->cur_flags);
gc = (font && font->valid) ? font->get_cache(wp->cur_size) : nullptr;
gc = (font && font->valid) ? font->get_cache(wp_points_to_pixels(wp->cur_size, layout_dpi)) : nullptr;
}
if (gc) {
*out_height = gc->line_height;
*out_ascent = gc->ascent;
} else {
*out_height = WP_DEFAULT_SIZE;
*out_ascent = WP_DEFAULT_SIZE;
*out_height = wp_points_to_pixels(WP_DEFAULT_SIZE, layout_dpi);
*out_ascent = *out_height;
}
}
static int wp_char_advance_at(WordProcessorState* wp, int abs_pos, char ch) {
static int wp_char_advance_at(WordProcessorState* wp, int abs_pos, int layout_dpi, char ch) {
if (ch == '\n') return 0;
if (!(ch >= 32 || ch < 0)) return 0;
TrueTypeFont* font = nullptr;
GlyphCache* gc = nullptr;
wp_font_at(wp, abs_pos, &font, &gc);
if (!font || !gc) return 8;
wp_font_at(wp, abs_pos, layout_dpi, &font, &gc);
if (!font || !gc) return wp_points_to_pixels(6, layout_dpi);
CachedGlyph* g = font->get_glyph(gc, (unsigned char)ch);
return g ? g->advance : 8;
return g ? g->advance : wp_points_to_pixels(6, layout_dpi);
}
static void wp_measure_line_range(WordProcessorState* wp, int start, int count,
static void wp_measure_line_range(WordProcessorState* wp, int start, int count, int layout_dpi,
int* out_width, int* out_height, int* out_ascent) {
int width = 0;
int height = 0;
@@ -198,11 +255,11 @@ static void wp_measure_line_range(WordProcessorState* wp, int start, int count,
for (int i = 0; i < count; i++) {
char ch = wp_char_at(wp, start + i);
if (ch != '\n')
width += wp_char_advance_at(wp, start + i, ch);
width += wp_char_advance_at(wp, start + i, layout_dpi, ch);
TrueTypeFont* font = nullptr;
GlyphCache* gc = nullptr;
wp_font_at(wp, start + i, &font, &gc);
wp_font_at(wp, start + i, layout_dpi, &font, &gc);
if (gc) {
if (gc->line_height > height) height = gc->line_height;
if (gc->ascent > ascent) ascent = gc->ascent;
@@ -210,7 +267,7 @@ static void wp_measure_line_range(WordProcessorState* wp, int start, int count,
}
if (height == 0)
wp_default_line_metrics(wp, start, &height, &ascent);
wp_default_line_metrics(wp, start, layout_dpi, &height, &ascent);
*out_width = width;
*out_height = height;
@@ -222,13 +279,41 @@ static int wp_line_spacing_advance(int height, int spacing_percent) {
return advance < height ? height : advance;
}
static int wp_effective_text_indent(const ParagraphStyle* para, bool first_line) {
static int wp_divider_height(uint8_t divider_type, int layout_dpi) {
int base = 14;
if (divider_type == PARA_DIVIDER_DOUBLE) base = 18;
else if (divider_type == PARA_DIVIDER_HEAVY) base = 16;
else if (divider_type == PARA_DIVIDER_THIN_THICK || divider_type == PARA_DIVIDER_THICK_THIN) base = 18;
int scaled = wp_scale_layout_units(base, layout_dpi);
return scaled > 0 ? scaled : 1;
}
static int wp_effective_text_indent(const ParagraphStyle* para, bool first_line, int layout_dpi) {
int indent = para->left_indent;
if (indent < 0) indent = 0;
if (first_line && para->list_type == PARA_LIST_NONE)
indent += para->first_line_indent;
if (indent < 0) indent = 0;
return indent;
return wp_scale_layout_units(indent, layout_dpi);
}
static void wp_prepare_paragraph_for_text_insert(WordProcessorState* wp, int abs_pos, char c) {
if (c == '\n' || wp->paragraph_count <= 0) return;
int para = wp_find_paragraph_at(wp, abs_pos);
if (para < 0 || para >= wp->paragraph_count) return;
ParagraphStyle* style = &wp->paragraphs[para];
if (style->divider_type == PARA_DIVIDER_NONE) return;
int para_start = 0;
int para_end = 0;
wp_paragraph_bounds(wp, para, &para_start, &para_end, nullptr);
if (para_start != para_end) return;
style->divider_type = PARA_DIVIDER_NONE;
wp->modified = true;
wp->wrap_dirty = true;
}
static bool wp_ensure_wrap_capacity(WordProcessorState* wp, int needed) {
@@ -412,6 +497,7 @@ void wp_init_empty_document(WordProcessorState* wp) {
wp->wrap_line_cap = 0;
wp->wrap_dirty = true;
wp->last_wrap_width = 0;
wp->last_wrap_dpi = 0;
wp->paragraph_count = 1;
wp_init_paragraph_style(&wp->paragraphs[0]);
@@ -433,6 +519,7 @@ void wp_init_empty_document(WordProcessorState* wp) {
wp->font_dropdown_open = false;
wp->size_dropdown_open = false;
wp->line_spacing_dropdown_open = false;
wp->divider_flyout_open = false;
wp->special_char_flyout_open = false;
wp->undo_count = 0;
@@ -531,6 +618,7 @@ void wp_insert_char(WordProcessorState* wp, char c) {
if (c == '\n' && wp->paragraph_count >= WP_MAX_PARAGRAPHS) return;
int insert_abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
wp_prepare_paragraph_for_text_insert(wp, insert_abs, c);
StyledRun* cur = &wp->runs[wp->cursor_run];
if (wp_same_style(cur, wp->cur_font_id, wp->cur_size, wp->cur_flags)) {
@@ -893,16 +981,23 @@ void wp_toggle_list(WordProcessorState* wp, uint8_t list_type) {
wp_selected_paragraph_range(wp, &para_s, &para_e);
bool turn_off = true;
bool has_target = false;
for (int i = para_s; i <= para_e && i < wp->paragraph_count; i++) {
if (wp->paragraphs[i].divider_type != PARA_DIVIDER_NONE)
continue;
has_target = true;
if (wp->paragraphs[i].list_type != list_type) {
turn_off = false;
break;
}
}
if (!has_target) return;
bool changed = false;
for (int i = para_s; i <= para_e && i < wp->paragraph_count; i++) {
ParagraphStyle* para = &wp->paragraphs[i];
if (para->divider_type != PARA_DIVIDER_NONE)
continue;
if (turn_off) {
if (para->list_type != PARA_LIST_NONE) {
para->list_type = PARA_LIST_NONE;
@@ -934,6 +1029,74 @@ void wp_toggle_list(WordProcessorState* wp, uint8_t list_type) {
}
}
void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type) {
if (!wp_valid_divider_type(divider_type)) return;
if (wp->has_selection)
wp_delete_selection(wp);
int abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
int para = wp_find_paragraph_at(wp, abs);
if (para < 0) para = 0;
if (para >= wp->paragraph_count) para = wp->paragraph_count - 1;
int para_start = 0;
int para_end = 0;
bool has_newline = false;
wp_paragraph_bounds(wp, para, &para_start, &para_end, &has_newline);
int needed_newlines = 0;
if (para_start == para_end) {
needed_newlines = has_newline ? 0 : 1;
} else if (abs == para_start) {
needed_newlines = 1;
} else if (abs == para_end) {
needed_newlines = has_newline ? 1 : 2;
} else {
needed_newlines = 2;
}
if (wp->paragraph_count + needed_newlines > WP_MAX_PARAGRAPHS)
return;
if (wp->total_text_len + needed_newlines > WP_MAX_TEXT - 1)
return;
ParagraphStyle divider_style = wp_make_divider_style(&wp->paragraphs[para], divider_type);
if (para_start == para_end) {
wp->paragraphs[para] = divider_style;
wp->modified = true;
wp->wrap_dirty = true;
if (!has_newline) {
wp_insert_char(wp, '\n');
} else {
wp_pos_to_run(wp, para_end + 1, &wp->cursor_run, &wp->cursor_offset);
}
return;
}
if (abs == para_start) {
wp_insert_char(wp, '\n');
wp->paragraphs[para] = divider_style;
wp_pos_to_run(wp, para_start + 1, &wp->cursor_run, &wp->cursor_offset);
} else if (abs == para_end) {
wp_insert_char(wp, '\n');
if (!has_newline)
wp_insert_char(wp, '\n');
wp->paragraphs[para + 1] = divider_style;
wp_pos_to_run(wp, abs + 2, &wp->cursor_run, &wp->cursor_offset);
} else {
wp_insert_char(wp, '\n');
wp_insert_char(wp, '\n');
wp->paragraphs[para + 1] = divider_style;
wp_pos_to_run(wp, abs + 2, &wp->cursor_run, &wp->cursor_offset);
}
wp->modified = true;
wp->wrap_dirty = true;
}
void wp_delete_selection(WordProcessorState* wp) {
if (!wp->has_selection) return;
@@ -952,13 +1115,14 @@ void wp_delete_selection(WordProcessorState* wp) {
wp_clear_selection(wp);
}
void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi) {
int wrap_width = content_w - WP_MARGIN * 2 - WP_SCROLLBAR_W;
if (wrap_width < 50) wrap_width = 50;
if (wrap_width != wp->last_wrap_width) {
if (wrap_width != wp->last_wrap_width || layout_dpi != wp->last_wrap_dpi) {
wp->wrap_dirty = true;
wp->last_wrap_width = wrap_width;
wp->last_wrap_dpi = layout_dpi;
}
if (!wp->wrap_dirty && wp->wrap_lines) return;
@@ -991,7 +1155,8 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
para_content_end++;
}
y += para->space_before;
bool is_divider = (para->divider_type != PARA_DIVIDER_NONE && para_start == para_content_end);
y += wp_scale_layout_units(para->space_before, layout_dpi);
bool first_line = true;
int line_start = para_start;
@@ -999,7 +1164,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
if (wp->wrap_line_count >= WP_MAX_WRAP_LINES) break;
if (!wp_ensure_wrap_capacity(wp, wp->wrap_line_count + 1)) break;
int text_indent = wp_effective_text_indent(para, first_line);
int text_indent = wp_effective_text_indent(para, first_line, layout_dpi);
int avail_width = wrap_width - text_indent;
if (avail_width < 40) avail_width = 40;
@@ -1010,7 +1175,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
while (scan < para_content_end) {
char ch = wp_char_at(wp, scan);
int cw = wp_char_advance_at(wp, scan, ch);
int cw = wp_char_advance_at(wp, scan, layout_dpi, ch);
if (width + cw > avail_width && scan > line_start) {
if (last_space >= line_start) {
scan = last_space + 1;
@@ -1028,7 +1193,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
}
if (scan == line_start && scan < para_content_end) {
width += wp_char_advance_at(wp, scan, wp_char_at(wp, scan));
width += wp_char_advance_at(wp, scan, layout_dpi, wp_char_at(wp, scan));
scan++;
}
@@ -1039,7 +1204,8 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
int line_width = 0;
int line_height = 0;
int line_ascent = 0;
wp_measure_line_range(wp, line_start, char_count, &line_width, &line_height, &line_ascent);
wp_measure_line_range(wp, line_start, char_count, layout_dpi,
&line_width, &line_height, &line_ascent);
WrapLine* line = &wp->wrap_lines[wp->wrap_line_count];
wp_pos_to_run(wp, line_start, &line->run_idx, &line->run_offset);
@@ -1050,6 +1216,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
line->width = line_width;
line->paragraph_idx = para_idx;
line->list_number = (first_line && para->list_type == PARA_LIST_NUMBER) ? list_number : 0;
line->divider_type = PARA_DIVIDER_NONE;
line->first_in_paragraph = first_line;
int extra = wrap_width - text_indent - line_width;
@@ -1075,25 +1242,35 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
wp_pos_to_run(wp, para_start, &line->run_idx, &line->run_offset);
line->char_count = has_newline ? 1 : 0;
line->y = y;
wp_default_line_metrics(wp, para_start, &line->height, &line->baseline);
line->width = 0;
line->paragraph_idx = para_idx;
line->list_number = (para->list_type == PARA_LIST_NUMBER) ? list_number : 0;
line->list_number = (is_divider || para->list_type != PARA_LIST_NUMBER) ? 0 : list_number;
line->divider_type = is_divider ? para->divider_type : (uint8_t)PARA_DIVIDER_NONE;
line->first_in_paragraph = true;
int text_indent = wp_effective_text_indent(para, true);
int text_indent = wp_effective_text_indent(para, true, layout_dpi);
if (is_divider) {
line->height = wp_divider_height(para->divider_type, layout_dpi);
line->baseline = line->height / 2;
line->x = WP_MARGIN + text_indent;
line->width = wrap_width - text_indent;
if (line->width < 24) line->width = 24;
} else {
wp_default_line_metrics(wp, para_start, layout_dpi, &line->height, &line->baseline);
line->width = 0;
int extra = wrap_width - text_indent;
if (extra < 0) extra = 0;
int x = WP_MARGIN + text_indent;
if (para->align == PARA_ALIGN_CENTER) x += extra / 2;
else if (para->align == PARA_ALIGN_RIGHT) x += extra;
line->x = x;
}
wp->wrap_line_count++;
y = line->y + line->height;
}
y += para->space_after;
y += wp_scale_layout_units(para->space_after, layout_dpi);
pos = para_content_end;
if (has_newline) pos++;
@@ -1112,11 +1289,12 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
line->run_offset = 0;
line->char_count = 0;
line->y = WP_MARGIN;
wp_default_line_metrics(wp, 0, &line->height, &line->baseline);
wp_default_line_metrics(wp, 0, layout_dpi, &line->height, &line->baseline);
line->x = WP_MARGIN;
line->width = 0;
line->paragraph_idx = 0;
line->list_number = 0;
line->divider_type = PARA_DIVIDER_NONE;
line->first_in_paragraph = true;
wp->wrap_line_count = 1;
@@ -1285,7 +1463,7 @@ static bool wp_serialize_document(WordProcessorState* wp, uint8_t** out_buf, int
buf[off++] = para->align;
buf[off++] = para->list_type;
buf[off++] = para->line_spacing;
buf[off++] = 0;
buf[off++] = para->divider_type;
wp_write_u16(buf, &off, (uint16_t)para->left_indent);
wp_write_u16(buf, &off, (uint16_t)para->first_line_indent);
wp_write_u16(buf, &off, (uint16_t)para->space_before);
@@ -1403,7 +1581,7 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
para->align = buf[off++];
para->list_type = buf[off++];
para->line_spacing = buf[off++];
off++;
para->divider_type = buf[off++];
para->left_indent = (int16_t)wp_read_u16(buf, &off);
para->first_line_indent = (int16_t)wp_read_u16(buf, &off);
para->space_before = (int16_t)wp_read_u16(buf, &off);
@@ -1411,6 +1589,7 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
if (para->align > PARA_ALIGN_RIGHT) para->align = PARA_ALIGN_LEFT;
if (para->list_type > PARA_LIST_NUMBER) para->list_type = PARA_LIST_NONE;
if (!wp_valid_divider_type(para->divider_type)) para->divider_type = PARA_DIVIDER_NONE;
bool known_spacing = false;
for (int j = 0; j < WP_LINE_SPACING_OPTION_COUNT; j++) {
@@ -1433,6 +1612,8 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
wp->scrollbar.scroll_offset = 0;
wp->modified = false;
wp->wrap_dirty = true;
wp->last_wrap_width = 0;
wp->last_wrap_dpi = 0;
wp->sel_anchor = 0;
wp->sel_end = 0;
wp->has_selection = false;
@@ -1440,6 +1621,8 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
wp->font_dropdown_open = false;
wp->size_dropdown_open = false;
wp->line_spacing_dropdown_open = false;
wp->divider_flyout_open = false;
wp->special_char_flyout_open = false;
wp->show_pathbar = false;
return true;
}
+46 -13
View File
@@ -46,10 +46,17 @@ static int wp_special_char_flyout_x() {
return dx < 0 ? 0 : dx;
}
static int wp_divider_flyout_x() {
int dx = WP_BTN_DIVIDER_X + 24 - WP_DIVIDER_FLYOUT_W;
if (dx + WP_DIVIDER_FLYOUT_W > g_win_w) dx = g_win_w - WP_DIVIDER_FLYOUT_W;
return dx < 0 ? 0 : dx;
}
static void wp_close_dropdowns(WordProcessorState* wp) {
wp->font_dropdown_open = false;
wp->size_dropdown_open = false;
wp->line_spacing_dropdown_open = false;
wp->divider_flyout_open = false;
wp->special_char_flyout_open = false;
}
@@ -59,6 +66,11 @@ static void wp_insert_special_char(WordProcessorState* wp, uint8_t ch) {
wp_history_checkpoint(wp);
}
static void wp_insert_divider_choice(WordProcessorState* wp, uint8_t divider_type) {
wp_insert_divider(wp, divider_type);
wp_history_checkpoint(wp);
}
static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, int edit_y) {
int click_y = local_y - edit_y + wp->scrollbar.scroll_offset;
@@ -72,6 +84,9 @@ static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, in
}
WrapLine* wl = &wp->wrap_lines[target_line];
if (wl->divider_type != PARA_DIVIDER_NONE)
return wp_wrap_line_start(wp, target_line);
int click_x = local_x - wl->x;
int ri = wl->run_idx;
int ro = wl->run_offset;
@@ -90,7 +105,7 @@ static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, in
continue;
}
GlyphCache* gc = font->get_cache(r->size);
GlyphCache* gc = font->get_cache(wp_screen_font_pixels(r->size));
int avail = r->len - ro;
int to_check = avail < chars_left ? avail : chars_left;
@@ -173,6 +188,19 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
return;
}
if (wp->divider_flyout_open && wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons)) {
int dx = wp_divider_flyout_x();
int dy = WP_TOOLBAR_H;
int dh = WP_DIVIDER_OPTION_COUNT * WP_DIVIDER_ROW_H + 4;
if (local_x >= dx && local_x < dx + WP_DIVIDER_FLYOUT_W && local_y >= dy && local_y < dy + dh) {
int idx = (local_y - dy - 2) / WP_DIVIDER_ROW_H;
if (idx >= 0 && idx < WP_DIVIDER_OPTION_COUNT)
wp_insert_divider_choice(wp, WP_DIVIDER_OPTIONS[idx].type);
}
wp->divider_flyout_open = false;
return;
}
if (wp->special_char_flyout_open && wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons)) {
int dx = wp_special_char_flyout_x();
int dy = WP_TOOLBAR_H;
@@ -222,15 +250,15 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
return;
}
if (local_x >= WP_FONT_DD_X && local_x < WP_FONT_DD_X + WP_FONT_DD_W && local_y >= 6 && local_y < 30) {
wp->font_dropdown_open = !wp->font_dropdown_open;
wp->size_dropdown_open = false;
wp->line_spacing_dropdown_open = false;
bool open = !wp->font_dropdown_open;
wp_close_dropdowns(wp);
wp->font_dropdown_open = open;
return;
}
if (local_x >= WP_SIZE_DD_X && local_x < WP_SIZE_DD_X + WP_SIZE_DD_W && local_y >= 6 && local_y < 30) {
wp->size_dropdown_open = !wp->size_dropdown_open;
wp->font_dropdown_open = false;
wp->line_spacing_dropdown_open = false;
bool open = !wp->size_dropdown_open;
wp_close_dropdowns(wp);
wp->size_dropdown_open = open;
return;
}
if (local_x >= WP_BTN_ALIGN_L_X && local_x < WP_BTN_ALIGN_L_X + 24 && local_y >= 6 && local_y < 30) {
@@ -276,10 +304,15 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
return;
}
if (local_x >= WP_LINE_DD_X && local_x < WP_LINE_DD_X + WP_LINE_DD_W && local_y >= 6 && local_y < 30) {
wp->line_spacing_dropdown_open = !wp->line_spacing_dropdown_open;
wp->font_dropdown_open = false;
wp->size_dropdown_open = false;
wp->special_char_flyout_open = false;
bool open = !wp->line_spacing_dropdown_open;
wp_close_dropdowns(wp);
wp->line_spacing_dropdown_open = open;
return;
}
if (local_x >= WP_BTN_DIVIDER_X && local_x < WP_BTN_DIVIDER_X + 24 && local_y >= 6 && local_y < 30) {
bool open = !wp->divider_flyout_open;
wp_close_dropdowns(wp);
wp->divider_flyout_open = open;
return;
}
if (local_x >= WP_BTN_SECTION_X && local_x < WP_BTN_SECTION_X + 24 && local_y >= 6 && local_y < 30) {
@@ -303,7 +336,7 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
return;
}
wp_recompute_wrap(wp, g_win_w);
wp_recompute_wrap(wp, g_win_w, WP_SCREEN_DPI);
if (wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons) &&
local_y >= edit_y && local_y < edit_y + text_area_h) {
@@ -382,7 +415,7 @@ void wp_handle_key(const Montauk::KeyEvent& key) {
}
wp_close_dropdowns(wp);
wp_recompute_wrap(wp, g_win_w);
wp_recompute_wrap(wp, g_win_w, WP_SCREEN_DPI);
if (key.ctrl && (key.ascii == 's' || key.ascii == 'S')) {
wp_save_file(wp);
+52 -7
View File
@@ -64,6 +64,43 @@ static int wp_print_layout_width() {
return (WP_PRINT_PAGE_W - WP_PRINT_MARGIN_X * 2) + WP_MARGIN * 2 + WP_SCROLLBAR_W;
}
static void wp_draw_print_divider(Canvas& c, int x, int y, int w, int h,
uint8_t divider_type, Color color) {
if (w <= 0 || h <= 0 || divider_type == PARA_DIVIDER_NONE) return;
int cy = y + h / 2;
switch (divider_type) {
case PARA_DIVIDER_SINGLE:
c.fill_rect(x, cy, w, 1, color);
break;
case PARA_DIVIDER_DOUBLE:
c.fill_rect(x, cy - 2, w, 1, color);
c.fill_rect(x, cy + 2, w, 1, color);
break;
case PARA_DIVIDER_DOTTED:
for (int dx = x; dx < x + w; dx += 5)
c.fill_rect(dx, cy, 2, 1, color);
break;
case PARA_DIVIDER_DASHED:
for (int dx = x; dx < x + w; dx += 11) {
int dash_w = (x + w - dx) < 7 ? (x + w - dx) : 7;
if (dash_w > 0) c.fill_rect(dx, cy, dash_w, 1, color);
}
break;
case PARA_DIVIDER_HEAVY:
c.fill_rect(x, cy - 1, w, 3, color);
break;
case PARA_DIVIDER_THIN_THICK:
c.fill_rect(x, cy - 3, w, 1, color);
c.fill_rect(x, cy, w, 3, color);
break;
case PARA_DIVIDER_THICK_THIN:
c.fill_rect(x, cy - 3, w, 3, color);
c.fill_rect(x, cy + 2, w, 1, color);
break;
}
}
static void wp_make_base_print_name(WordProcessorState* wp, char* out, int out_len) {
const char* src = wp->filename[0] ? wp->filename : "document";
int last_dot = -1;
@@ -109,7 +146,7 @@ static bool wp_build_print_page_map(WordProcessorState* wp,
if (out_line_y) *out_line_y = nullptr;
if (out_page_count) *out_page_count = 0;
wp_recompute_wrap(wp, wp_print_layout_width());
wp_recompute_wrap(wp, wp_print_layout_width(), WP_PRINT_DPI);
if (wp->wrap_line_count <= 0) {
wp_status_copy(err, err_len, "failed to paginate document");
return false;
@@ -164,14 +201,17 @@ static void wp_draw_print_list_marker(Canvas& c, WordProcessorState* wp, WrapLin
return;
ParagraphStyle* para = &wp->paragraphs[wl->paragraph_idx];
if (para->list_type == PARA_LIST_NONE) return;
if (para->divider_type != PARA_DIVIDER_NONE || para->list_type == PARA_LIST_NONE) return;
int marker_x = WP_PRINT_MARGIN_X + para->left_indent + para->first_line_indent;
int marker_x = WP_PRINT_MARGIN_X + wp_scale_layout_units(para->left_indent + para->first_line_indent, WP_PRINT_DPI);
int min_x = WP_PRINT_MARGIN_X - WP_MARGIN + 4;
if (marker_x < min_x) marker_x = min_x;
if (para->list_type == PARA_LIST_BULLET) {
fill_circle(c, marker_x + 7, py + wl->height / 2, 3, colors::BLACK);
int bullet_x = marker_x + wp_scale_layout_units(7, WP_PRINT_DPI);
int bullet_r = wp_scale_layout_units(3, WP_PRINT_DPI);
if (bullet_r < 2) bullet_r = 2;
fill_circle(c, bullet_x, py + wl->height / 2, bullet_r, colors::BLACK);
return;
}
@@ -180,8 +220,9 @@ static void wp_draw_print_list_marker(Canvas& c, WordProcessorState* wp, WrapLin
TrueTypeFont* font = wp_get_font(run->font_id, run->flags);
char label[16];
snprintf(label, sizeof(label), "%d.", wl->list_number > 0 ? wl->list_number : 1);
int top_y = py + (wl->height - run->size) / 2;
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::BLACK, run->size);
int font_px = wp_print_font_pixels(run->size);
int top_y = py + (wl->height - font_px) / 2;
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::BLACK, font_px);
}
static bool wp_encode_print_page(WordProcessorState* wp,
@@ -210,6 +251,10 @@ static bool wp_encode_print_page(WordProcessorState* wp,
WrapLine* wl = &wp->wrap_lines[li];
int py = line_y[li];
wp_draw_print_list_marker(c, wp, wl, py);
if (wl->divider_type != PARA_DIVIDER_NONE) {
int divider_x = WP_PRINT_MARGIN_X + (wl->x - WP_MARGIN);
wp_draw_print_divider(c, divider_x, py, wl->width, wl->height, wl->divider_type, colors::BLACK);
}
int chars_left = wl->char_count;
int ri = wl->run_idx;
@@ -228,7 +273,7 @@ static bool wp_encode_print_page(WordProcessorState* wp,
continue;
}
GlyphCache* gc = font->get_cache(r->size);
GlyphCache* gc = font->get_cache(wp_print_font_pixels(r->size));
int baseline = py + wl->baseline;
int avail = r->len - ro;
+106 -8
View File
@@ -41,6 +41,49 @@ static int wp_special_char_flyout_x() {
return dx < 0 ? 0 : dx;
}
static int wp_divider_flyout_x() {
int dx = WP_BTN_DIVIDER_X + 24 - WP_DIVIDER_FLYOUT_W;
if (dx + WP_DIVIDER_FLYOUT_W > g_win_w) dx = g_win_w - WP_DIVIDER_FLYOUT_W;
return dx < 0 ? 0 : dx;
}
static void wp_draw_divider_sample(Canvas& c, int x, int y, int w, int h,
uint8_t divider_type, Color color) {
if (w <= 0 || h <= 0 || divider_type == PARA_DIVIDER_NONE) return;
int cy = y + h / 2;
switch (divider_type) {
case PARA_DIVIDER_SINGLE:
c.fill_rect(x, cy, w, 1, color);
break;
case PARA_DIVIDER_DOUBLE:
c.fill_rect(x, cy - 2, w, 1, color);
c.fill_rect(x, cy + 2, w, 1, color);
break;
case PARA_DIVIDER_DOTTED:
for (int dx = x; dx < x + w; dx += 5)
c.fill_rect(dx, cy, 2, 1, color);
break;
case PARA_DIVIDER_DASHED:
for (int dx = x; dx < x + w; dx += 11) {
int dash_w = (x + w - dx) < 7 ? (x + w - dx) : 7;
if (dash_w > 0) c.fill_rect(dx, cy, dash_w, 1, color);
}
break;
case PARA_DIVIDER_HEAVY:
c.fill_rect(x, cy - 1, w, 3, color);
break;
case PARA_DIVIDER_THIN_THICK:
c.fill_rect(x, cy - 3, w, 1, color);
c.fill_rect(x, cy, w, 3, color);
break;
case PARA_DIVIDER_THICK_THIN:
c.fill_rect(x, cy - 3, w, 3, color);
c.fill_rect(x, cy + 2, w, 1, color);
break;
}
}
static void wp_fit_ui_text(const char* src, char* out, int out_len, int max_w) {
if (!src || !src[0]) {
if (out_len > 0) out[0] = '\0';
@@ -71,7 +114,7 @@ static void wp_fit_ui_text(const char* src, char* out, int out_len, int max_w) {
static ParagraphStyle* wp_current_paragraph_style(WordProcessorState* wp) {
if (wp->paragraph_count <= 0) {
static ParagraphStyle fallback = { PARA_ALIGN_LEFT, PARA_LIST_NONE, 100, 0, 0, 0, 0, 0 };
static ParagraphStyle fallback = { PARA_ALIGN_LEFT, PARA_LIST_NONE, 100, PARA_DIVIDER_NONE, 0, 0, 0, 0 };
return &fallback;
}
int para = wp_find_paragraph_at(wp, wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset));
@@ -96,18 +139,34 @@ static const char* wp_list_label(uint8_t list_type) {
}
}
static const char* wp_divider_label(uint8_t divider_type) {
switch (divider_type) {
case PARA_DIVIDER_SINGLE: return "Single";
case PARA_DIVIDER_DOUBLE: return "Double";
case PARA_DIVIDER_DOTTED: return "Dotted";
case PARA_DIVIDER_DASHED: return "Dashed";
case PARA_DIVIDER_HEAVY: return "Heavy";
case PARA_DIVIDER_THIN_THICK: return "Thin/Thick";
case PARA_DIVIDER_THICK_THIN: return "Thick/Thin";
default: return "None";
}
}
static void wp_draw_list_marker(Canvas& c, WordProcessorState* wp, WrapLine* wl, int py) {
if (!wl->first_in_paragraph || wl->paragraph_idx < 0 || wl->paragraph_idx >= wp->paragraph_count)
return;
ParagraphStyle* para = &wp->paragraphs[wl->paragraph_idx];
if (para->list_type == PARA_LIST_NONE) return;
if (para->divider_type != PARA_DIVIDER_NONE || para->list_type == PARA_LIST_NONE) return;
int marker_x = WP_MARGIN + para->left_indent + para->first_line_indent;
int marker_x = WP_MARGIN + wp_scale_layout_units(para->left_indent + para->first_line_indent, WP_SCREEN_DPI);
if (marker_x < 4) marker_x = 4;
if (para->list_type == PARA_LIST_BULLET) {
fill_circle(c, marker_x + 7, py + wl->height / 2, 3, colors::TEXT_COLOR);
int bullet_x = marker_x + wp_scale_layout_units(7, WP_SCREEN_DPI);
int bullet_r = wp_scale_layout_units(3, WP_SCREEN_DPI);
if (bullet_r < 2) bullet_r = 2;
fill_circle(c, bullet_x, py + wl->height / 2, bullet_r, colors::TEXT_COLOR);
return;
}
@@ -116,8 +175,9 @@ static void wp_draw_list_marker(Canvas& c, WordProcessorState* wp, WrapLine* wl,
if (wp->run_count <= 0) return;
StyledRun* run = &wp->runs[wl->run_idx < wp->run_count ? wl->run_idx : 0];
TrueTypeFont* font = wp_get_font(run->font_id, run->flags);
int top_y = py + (wl->height - run->size) / 2;
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::TEXT_COLOR, run->size);
int font_px = wp_screen_font_pixels(run->size);
int top_y = py + (wl->height - font_px) / 2;
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::TEXT_COLOR, font_px);
}
void wp_render() {
@@ -203,6 +263,17 @@ void wp_render() {
wp_draw_ui_text(c, WP_LINE_DD_X + 8, (WP_TOOLBAR_H - sfh) / 2, spacing, colors::TEXT_COLOR);
}
c.vline(WP_BTN_DIVIDER_X - 4, 4, 28, colors::BORDER);
Color divider_bg = (wp->divider_flyout_open || cur_para->divider_type != PARA_DIVIDER_NONE) ? btn_active : btn_bg;
c.fill_rounded_rect(WP_BTN_DIVIDER_X, 6, 24, 24, 3, divider_bg);
uint8_t divider_preview = cur_para->divider_type != PARA_DIVIDER_NONE
? cur_para->divider_type
: (uint8_t)PARA_DIVIDER_SINGLE;
wp_draw_divider_sample(c, WP_BTN_DIVIDER_X + 4, 6, 16, 24,
divider_preview,
colors::TEXT_COLOR);
c.vline(WP_BTN_SECTION_X - 4, 4, 28, colors::BORDER);
Color special_bg = wp->special_char_flyout_open ? btn_active : btn_bg;
@@ -250,7 +321,7 @@ void wp_render() {
}
int text_area_h = c.h - edit_y - WP_STATUS_H;
wp_recompute_wrap(wp, c.w);
wp_recompute_wrap(wp, c.w, WP_SCREEN_DPI);
wp->scrollbar.bounds = {c.w - WP_SCROLLBAR_W, edit_y, WP_SCROLLBAR_W, text_area_h};
wp->scrollbar.content_height = wp->content_height;
@@ -278,6 +349,8 @@ void wp_render() {
if (py >= edit_y + text_area_h) break;
wp_draw_list_marker(c, wp, wl, py);
if (wl->divider_type != PARA_DIVIDER_NONE)
wp_draw_divider_sample(c, wl->x, py, wl->width, wl->height, wl->divider_type, colors::TEXT_COLOR);
int chars_left = wl->char_count;
int ri = wl->run_idx;
@@ -295,7 +368,7 @@ void wp_render() {
continue;
}
GlyphCache* gc = font->get_cache(r->size);
GlyphCache* gc = font->get_cache(wp_screen_font_pixels(r->size));
int baseline = py + wl->baseline;
int avail = r->len - ro;
@@ -369,11 +442,18 @@ void wp_render() {
char status_left[320];
const char* name = wp->filename[0] ? wp->filename : "Untitled";
if (cur_para->divider_type != PARA_DIVIDER_NONE) {
snprintf(status_left, sizeof(status_left), " %s%s | %s %dpt | Divider: %s | %d%%",
name, wp->modified ? " *" : "",
WP_FONT_NAMES[wp->cur_font_id < FONT_COUNT ? wp->cur_font_id : 0], (int)wp->cur_size,
wp_divider_label(cur_para->divider_type), (int)cur_para->line_spacing);
} else {
snprintf(status_left, sizeof(status_left), " %s%s | %s %dpt | %s | %s | %d%%",
name, wp->modified ? " *" : "",
WP_FONT_NAMES[wp->cur_font_id < FONT_COUNT ? wp->cur_font_id : 0], (int)wp->cur_size,
wp_align_label(cur_para->align), wp_list_label(cur_para->list_type),
(int)cur_para->line_spacing);
}
char status_right[32];
snprintf(status_right, sizeof(status_right), "%d chars ", wp->total_text_len);
@@ -437,6 +517,24 @@ void wp_render() {
}
}
if (wp->divider_flyout_open) {
int dx = wp_divider_flyout_x();
int dy = WP_TOOLBAR_H;
int dw = WP_DIVIDER_FLYOUT_W;
int dh = WP_DIVIDER_OPTION_COUNT * WP_DIVIDER_ROW_H + 4;
c.fill_rect(dx, dy, dw, dh, colors::MENU_BG);
c.rect(dx, dy, dw, dh, colors::BORDER);
for (int i = 0; i < WP_DIVIDER_OPTION_COUNT; i++) {
int iy = dy + 2 + i * WP_DIVIDER_ROW_H;
if (WP_DIVIDER_OPTIONS[i].type == cur_para->divider_type)
c.fill_rect(dx + 2, iy, dw - 4, WP_DIVIDER_ROW_H - 2, colors::MENU_HOVER);
wp_draw_divider_sample(c, dx + 10, iy, 56, WP_DIVIDER_ROW_H,
WP_DIVIDER_OPTIONS[i].type, colors::TEXT_COLOR);
wp_draw_ui_text(c, dx + 78, iy + (WP_DIVIDER_ROW_H - sfh) / 2,
WP_DIVIDER_OPTIONS[i].label, colors::TEXT_COLOR);
}
}
if (wp->special_char_flyout_open) {
int dx = wp_special_char_flyout_x();
int dy = WP_TOOLBAR_H;
+63 -4
View File
@@ -34,6 +34,8 @@ static constexpr int WP_MAX_WRAP_LINES = 4096;
static constexpr int WP_MAX_PARAGRAPHS = 4096;
static constexpr int WP_DEFAULT_SIZE = 18;
static constexpr int WP_UNDO_MAX = 24;
static constexpr int WP_SCREEN_DPI = 96;
static constexpr int WP_PRINT_DPI = 150;
static constexpr int WP_PARA_STEP = 12;
static constexpr int WP_SPACE_STEP = 6;
static constexpr int WP_LIST_LEFT = 28;
@@ -59,7 +61,10 @@ static constexpr int WP_BTN_OUTDENT_X = 496;
static constexpr int WP_BTN_INDENT_X = 524;
static constexpr int WP_LINE_DD_X = 558;
static constexpr int WP_LINE_DD_W = 48;
static constexpr int WP_BTN_SECTION_X = 614;
static constexpr int WP_BTN_DIVIDER_X = 614;
static constexpr int WP_BTN_SECTION_X = 646;
static constexpr int WP_DIVIDER_ROW_H = 24;
static constexpr int WP_DIVIDER_FLYOUT_W = 216;
static constexpr int WP_SPECIAL_CHAR_ROW_H = 24;
static constexpr int WP_SPECIAL_CHAR_FLYOUT_W = 144;
@@ -82,20 +87,48 @@ static constexpr int WP_SIZE_OPTION_COUNT = 8;
inline constexpr int WP_LINE_SPACING_OPTIONS[] = { 100, 125, 150, 200 };
static constexpr int WP_LINE_SPACING_OPTION_COUNT = 4;
inline int wp_points_to_pixels(int points, int dpi) {
if (points <= 0) points = 1;
if (dpi <= 0) dpi = WP_SCREEN_DPI;
int px = (points * dpi + 36) / 72;
return px > 0 ? px : 1;
}
inline int wp_screen_font_pixels(int points) {
return wp_points_to_pixels(points, WP_SCREEN_DPI);
}
inline int wp_print_font_pixels(int points) {
return wp_points_to_pixels(points, WP_PRINT_DPI);
}
inline int wp_scale_layout_units(int units, int dpi) {
if (dpi <= 0) dpi = WP_SCREEN_DPI;
int scaled = (units * dpi + (units >= 0 ? WP_SCREEN_DPI / 2 : -WP_SCREEN_DPI / 2)) / WP_SCREEN_DPI;
return scaled;
}
struct WpSpecialCharOption {
uint8_t code;
const char* label;
};
struct WpDividerOption {
uint8_t type;
const char* label;
};
inline constexpr WpSpecialCharOption WP_SPECIAL_CHAR_OPTIONS[] = {
{ 0xA7, "Section" },
{ 0xB6, "Pilcrow" },
{ 0x96, "En Dash" },
{ 0x97, "Em Dash" },
{ 0xA9, "Copyright" },
{ 0xAE, "Registered" },
{ 0xB0, "Degree" },
{ 0xB1, "Plus/Minus" },
};
static constexpr int WP_SPECIAL_CHAR_OPTION_COUNT = 6;
static constexpr int WP_SPECIAL_CHAR_OPTION_COUNT = 8;
enum ParagraphAlign : uint8_t {
PARA_ALIGN_LEFT = 0,
@@ -109,6 +142,28 @@ enum ParagraphListType : uint8_t {
PARA_LIST_NUMBER = 2,
};
enum ParagraphDividerType : uint8_t {
PARA_DIVIDER_NONE = 0,
PARA_DIVIDER_SINGLE = 1,
PARA_DIVIDER_DOUBLE = 2,
PARA_DIVIDER_DOTTED = 3,
PARA_DIVIDER_DASHED = 4,
PARA_DIVIDER_HEAVY = 5,
PARA_DIVIDER_THIN_THICK = 6,
PARA_DIVIDER_THICK_THIN = 7,
};
inline constexpr WpDividerOption WP_DIVIDER_OPTIONS[] = {
{ PARA_DIVIDER_SINGLE, "Single Line" },
{ PARA_DIVIDER_DOUBLE, "Double Line" },
{ PARA_DIVIDER_DOTTED, "Dotted Line" },
{ PARA_DIVIDER_DASHED, "Dashed Line" },
{ PARA_DIVIDER_HEAVY, "Heavy Line" },
{ PARA_DIVIDER_THIN_THICK, "Thin/Thick Rule" },
{ PARA_DIVIDER_THICK_THIN, "Thick/Thin Rule" },
};
static constexpr int WP_DIVIDER_OPTION_COUNT = 7;
struct WPFontTable {
TrueTypeFont* fonts[FONT_COUNT][4];
bool loaded;
@@ -134,6 +189,7 @@ struct WrapLine {
int width;
int paragraph_idx;
int list_number;
uint8_t divider_type;
bool first_in_paragraph;
};
@@ -141,7 +197,7 @@ struct ParagraphStyle {
uint8_t align;
uint8_t list_type;
uint8_t line_spacing;
uint8_t _pad;
uint8_t divider_type;
int16_t left_indent;
int16_t first_line_indent;
int16_t space_before;
@@ -277,6 +333,7 @@ struct WordProcessorState {
int wrap_line_cap;
bool wrap_dirty;
int last_wrap_width;
int last_wrap_dpi;
ParagraphStyle paragraphs[WP_MAX_PARAGRAPHS];
int paragraph_count;
@@ -298,6 +355,7 @@ struct WordProcessorState {
bool font_dropdown_open;
bool size_dropdown_open;
bool line_spacing_dropdown_open;
bool divider_flyout_open;
bool special_char_flyout_open;
UndoSnapshot undo[WP_UNDO_MAX];
@@ -362,9 +420,10 @@ void wp_adjust_paragraph_spacing_after(WordProcessorState* wp, int delta);
void wp_cycle_line_spacing(WordProcessorState* wp);
void wp_set_line_spacing(WordProcessorState* wp, int value);
void wp_toggle_list(WordProcessorState* wp, uint8_t list_type);
void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type);
void wp_delete_selection(WordProcessorState* wp);
void wp_recompute_wrap(WordProcessorState* wp, int content_w);
void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi);
int wp_find_wrap_line(WordProcessorState* wp, int abs_pos);
int wp_wrap_line_start(WordProcessorState* wp, int line_idx);
void wp_ensure_cursor_visible(WordProcessorState* wp, int view_h);