mirror of https://github.com/OpenTTD/OpenTTD
Change: Reflow Textfile window content incrementally.
This avoids a stall when reflowing a long text file with some truetype fonts.pull/14293/head
parent
46b745a06a
commit
7344dfe651
|
@ -105,40 +105,25 @@ void TextfileWindow::ConstructWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the total height of the content displayed in this window, if wrapping is disabled.
|
* Reset the reflow process to start on the next UI tick.
|
||||||
* @return the height in pixels
|
|
||||||
*/
|
*/
|
||||||
uint TextfileWindow::ReflowContent()
|
void TextfileWindow::ReflowContent()
|
||||||
{
|
{
|
||||||
uint height = 0;
|
/* Minimum number of lines that will be flowed. */
|
||||||
if (!this->IsTextWrapped()) {
|
if (this->num_lines == 0) this->num_lines = std::size(this->lines);
|
||||||
for (auto &line : this->lines) {
|
|
||||||
line.top = height;
|
|
||||||
height++;
|
|
||||||
line.bottom = height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int max_width = this->GetWidget<NWidgetCore>(WID_TF_BACKGROUND)->current_x - WidgetDimensions::scaled.frametext.Horizontal();
|
|
||||||
for (auto &line : this->lines) {
|
|
||||||
line.top = height;
|
|
||||||
height += GetStringHeight(line.text, max_width, FS_MONO) / GetCharacterHeight(FS_MONO);
|
|
||||||
line.bottom = height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return height;
|
auto it = this->GetIteratorFromPosition(this->vscroll->GetPosition());
|
||||||
}
|
|
||||||
|
|
||||||
uint TextfileWindow::GetContentHeight()
|
auto adapter = AlternatingView{this->lines, it};
|
||||||
{
|
this->reflow_iter = adapter.begin();
|
||||||
if (this->lines.empty()) return 0;
|
this->reflow_end = adapter.end();
|
||||||
return this->lines.back().bottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual */ void TextfileWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize)
|
/* virtual */ void TextfileWindow::UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize)
|
||||||
{
|
{
|
||||||
switch (widget) {
|
switch (widget) {
|
||||||
case WID_TF_BACKGROUND:
|
case WID_TF_BACKGROUND:
|
||||||
|
resize.width = GetCharacterHeight(FS_MONO); // Width is not available here as the font may not be loaded yet.
|
||||||
resize.height = GetCharacterHeight(FS_MONO);
|
resize.height = GetCharacterHeight(FS_MONO);
|
||||||
|
|
||||||
size.height = 4 * resize.height + WidgetDimensions::scaled.frametext.Vertical(); // At least 4 lines are visible.
|
size.height = 4 * resize.height + WidgetDimensions::scaled.frametext.Vertical(); // At least 4 lines are visible.
|
||||||
|
@ -148,19 +133,13 @@ uint TextfileWindow::GetContentHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set scrollbars to the right lengths. */
|
/** Set scrollbars to the right lengths. */
|
||||||
void TextfileWindow::SetupScrollbars(bool force_reflow)
|
void TextfileWindow::SetupScrollbars()
|
||||||
{
|
{
|
||||||
if (this->IsTextWrapped()) {
|
this->vscroll->SetCount(this->num_lines);
|
||||||
/* Reflow is mandatory if text wrapping is on */
|
this->hscroll->SetCount(this->IsTextWrapped() ? 0 : CeilDiv(this->max_width, this->resize.step_width));
|
||||||
uint height = this->ReflowContent();
|
|
||||||
this->vscroll->SetCount(height);
|
|
||||||
this->hscroll->SetCount(0);
|
|
||||||
} else {
|
|
||||||
uint height = force_reflow ? this->ReflowContent() : this->GetContentHeight();
|
|
||||||
this->vscroll->SetCount(height);
|
|
||||||
this->hscroll->SetCount(this->max_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this->SetWidgetDirty(WID_TF_VSCROLLBAR);
|
||||||
|
this->SetWidgetDirty(WID_TF_HSCROLLBAR);
|
||||||
this->SetWidgetDisabledState(WID_TF_HSCROLLBAR, this->IsTextWrapped());
|
this->SetWidgetDisabledState(WID_TF_HSCROLLBAR, this->IsTextWrapped());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,18 +287,17 @@ const TextfileWindow::Hyperlink *TextfileWindow::GetHyperlink(Point pt) const
|
||||||
|
|
||||||
/* Which line was clicked. */
|
/* Which line was clicked. */
|
||||||
const int clicked_row = this->GetRowFromWidget(pt.y, WID_TF_BACKGROUND, WidgetDimensions::scaled.frametext.top, GetCharacterHeight(FS_MONO)) + this->GetScrollbar(WID_TF_VSCROLLBAR)->GetPosition();
|
const int clicked_row = this->GetRowFromWidget(pt.y, WID_TF_BACKGROUND, WidgetDimensions::scaled.frametext.top, GetCharacterHeight(FS_MONO)) + this->GetScrollbar(WID_TF_VSCROLLBAR)->GetPosition();
|
||||||
size_t line_index;
|
|
||||||
size_t subline;
|
int visible_line = 0;
|
||||||
if (this->IsTextWrapped()) {
|
auto it = std::ranges::find_if(this->lines, [&visible_line, clicked_row](const Line &l) {
|
||||||
auto it = std::ranges::find_if(this->lines, [clicked_row](const Line &l) { return l.top <= clicked_row && l.bottom > clicked_row; });
|
visible_line += l.num_lines;
|
||||||
|
return (visible_line - l.num_lines) <= clicked_row && visible_line > clicked_row;
|
||||||
|
});
|
||||||
if (it == this->lines.cend()) return nullptr;
|
if (it == this->lines.cend()) return nullptr;
|
||||||
line_index = it - this->lines.cbegin();
|
|
||||||
subline = clicked_row - it->top;
|
size_t line_index = it - this->lines.cbegin();
|
||||||
Debug(misc, 4, "TextfileWindow check hyperlink: clicked_row={}, line_index={}, line.top={}, subline={}", clicked_row, line_index, it->top, subline);
|
size_t subline = clicked_row - (visible_line - it->num_lines);
|
||||||
} else {
|
Debug(misc, 4, "TextfileWindow check hyperlink: clicked_row={}, line_index={}, line.top={}, subline={}", clicked_row, line_index, visible_line - it->num_lines, subline);
|
||||||
line_index = clicked_row / GetCharacterHeight(FS_MONO);
|
|
||||||
subline = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find hyperlinks in this line. */
|
/* Find hyperlinks in this line. */
|
||||||
std::vector<const Hyperlink *> found_links;
|
std::vector<const Hyperlink *> found_links;
|
||||||
|
@ -329,12 +307,12 @@ const TextfileWindow::Hyperlink *TextfileWindow::GetHyperlink(Point pt) const
|
||||||
if (found_links.empty()) return nullptr;
|
if (found_links.empty()) return nullptr;
|
||||||
|
|
||||||
/* Build line layout to figure out character position that was clicked. */
|
/* Build line layout to figure out character position that was clicked. */
|
||||||
uint window_width = this->IsTextWrapped() ? this->GetWidget<NWidgetCore>(WID_TF_BACKGROUND)->current_x - WidgetDimensions::scaled.frametext.Horizontal() : INT_MAX;
|
const Line &line = this->lines[line_index];
|
||||||
Layouter layout(this->lines[line_index].text, window_width, FS_MONO);
|
Layouter layout(line.text, line.wrapped_width, FS_MONO);
|
||||||
assert(subline < layout.size());
|
assert(subline < layout.size());
|
||||||
ptrdiff_t char_index = layout.GetCharAtPosition(pt.x - WidgetDimensions::scaled.frametext.left, subline);
|
ptrdiff_t char_index = layout.GetCharAtPosition(pt.x - WidgetDimensions::scaled.frametext.left, subline);
|
||||||
if (char_index < 0) return nullptr;
|
if (char_index < 0) return nullptr;
|
||||||
Debug(misc, 4, "TextfileWindow check hyperlink click: line={}, subline={}, char_index={}", line_index, subline, (int)char_index);
|
Debug(misc, 4, "TextfileWindow check hyperlink click: line={}, subline={}, char_index={}", line_index, subline, char_index);
|
||||||
|
|
||||||
/* Found character index in line, check if any links are at that position. */
|
/* Found character index in line, check if any links are at that position. */
|
||||||
for (const Hyperlink *link : found_links) {
|
for (const Hyperlink *link : found_links) {
|
||||||
|
@ -570,6 +548,14 @@ void TextfileWindow::AfterLoadMarkdown()
|
||||||
|
|
||||||
/* virtual */ void TextfileWindow::DrawWidget(const Rect &r, WidgetID widget) const
|
/* virtual */ void TextfileWindow::DrawWidget(const Rect &r, WidgetID widget) const
|
||||||
{
|
{
|
||||||
|
if (widget == WID_TF_CAPTION && std::size(this->lines) > 0 && this->reflow_iter != this->reflow_end) {
|
||||||
|
/* Draw a progress bar in the caption. */
|
||||||
|
Rect fr = r.Shrink(WidgetDimensions::scaled.captiontext).WithHeight(WidgetDimensions::scaled.vsep_normal, true);
|
||||||
|
size_t remaining = std::distance(this->reflow_iter, this->reflow_end);
|
||||||
|
fr = fr.WithWidth(static_cast<int>(remaining * fr.Width() / std::size(this->lines)), _current_text_dir != TD_RTL);
|
||||||
|
GfxFillRect(fr, PC_WHITE, FILLRECT_CHECKER);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget != WID_TF_BACKGROUND) return;
|
if (widget != WID_TF_BACKGROUND) return;
|
||||||
|
|
||||||
Rect fr = r.Shrink(WidgetDimensions::scaled.frametext);
|
Rect fr = r.Shrink(WidgetDimensions::scaled.frametext);
|
||||||
|
@ -582,18 +568,21 @@ void TextfileWindow::AfterLoadMarkdown()
|
||||||
fr = fr.Translate(-fr.left, -fr.top);
|
fr = fr.Translate(-fr.left, -fr.top);
|
||||||
int line_height = GetCharacterHeight(FS_MONO);
|
int line_height = GetCharacterHeight(FS_MONO);
|
||||||
|
|
||||||
if (!this->IsTextWrapped()) fr = ScrollRect(fr, *this->hscroll, 1);
|
if (!this->IsTextWrapped()) fr = ScrollRect(fr, *this->hscroll, this->resize.step_width);
|
||||||
|
|
||||||
int pos = this->vscroll->GetPosition();
|
int pos = this->vscroll->GetPosition();
|
||||||
int cap = this->vscroll->GetCapacity();
|
int cap = this->vscroll->GetCapacity();
|
||||||
|
int cur_line = 0;
|
||||||
for (auto &line : this->lines) {
|
for (auto &line : this->lines) {
|
||||||
if (line.bottom < pos) continue;
|
int top = cur_line;
|
||||||
if (line.top > pos + cap) break;
|
cur_line += line.num_lines;
|
||||||
|
if (cur_line <= pos) continue;
|
||||||
|
if (top > pos + cap) break;
|
||||||
|
|
||||||
int y_offset = (line.top - pos) * line_height;
|
int y_offset = (top - pos) * line_height;
|
||||||
if (this->IsTextWrapped()) {
|
if (line.wrapped_width != 0) {
|
||||||
DrawStringMultiLineWithClipping(fr.left, fr.right, y_offset, y_offset + (line.bottom - line.top) * line_height, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO);
|
Rect tr = fr.WithWidth(line.wrapped_width, _current_text_dir == TD_RTL);
|
||||||
|
DrawStringMultiLineWithClipping(tr.left, tr.right, y_offset, y_offset + line.num_lines * line_height, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO);
|
||||||
} else {
|
} else {
|
||||||
DrawString(fr.left, fr.right, y_offset, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO);
|
DrawString(fr.left, fr.right, y_offset, line.text, line.colour, SA_TOP | SA_LEFT, false, FS_MONO);
|
||||||
}
|
}
|
||||||
|
@ -605,14 +594,31 @@ void TextfileWindow::AfterLoadMarkdown()
|
||||||
this->vscroll->SetCapacityFromWidget(this, WID_TF_BACKGROUND, WidgetDimensions::scaled.frametext.Vertical());
|
this->vscroll->SetCapacityFromWidget(this, WID_TF_BACKGROUND, WidgetDimensions::scaled.frametext.Vertical());
|
||||||
this->hscroll->SetCapacityFromWidget(this, WID_TF_BACKGROUND, WidgetDimensions::scaled.framerect.Horizontal());
|
this->hscroll->SetCapacityFromWidget(this, WID_TF_BACKGROUND, WidgetDimensions::scaled.framerect.Horizontal());
|
||||||
|
|
||||||
this->SetupScrollbars(false);
|
this->UpdateVisibleIterators();
|
||||||
|
this->ReflowContent();
|
||||||
|
this->SetupScrollbars();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual */ void TextfileWindow::OnInit()
|
||||||
|
{
|
||||||
|
/* If font has changed we need to recalculate the maximum width. */
|
||||||
|
this->num_lines = 0;
|
||||||
|
this->max_width = 0;
|
||||||
|
for (auto &line : this->lines) {
|
||||||
|
line.max_width = -1;
|
||||||
|
line.num_lines = 1;
|
||||||
|
line.wrapped_width = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->ReflowContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual */ void TextfileWindow::OnInvalidateData([[maybe_unused]] int data, [[maybe_unused]] bool gui_scope)
|
/* virtual */ void TextfileWindow::OnInvalidateData([[maybe_unused]] int data, [[maybe_unused]] bool gui_scope)
|
||||||
{
|
{
|
||||||
if (!gui_scope) return;
|
if (!gui_scope) return;
|
||||||
|
|
||||||
this->SetupScrollbars(true);
|
this->ReflowContent();
|
||||||
|
this->SetupScrollbars();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextfileWindow::OnDropdownSelect(WidgetID widget, int index)
|
void TextfileWindow::OnDropdownSelect(WidgetID widget, int index)
|
||||||
|
@ -622,16 +628,107 @@ void TextfileWindow::OnDropdownSelect(WidgetID widget, int index)
|
||||||
this->ScrollToLine(index);
|
this->ScrollToLine(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern bool CanContinueRealtimeTick();
|
||||||
|
|
||||||
|
TextfileWindow::ReflowState TextfileWindow::ContinueReflow()
|
||||||
|
{
|
||||||
|
if (this->reflow_iter == this->reflow_end) return ReflowState::None;
|
||||||
|
|
||||||
|
int window_width = this->GetWidget<NWidgetCore>(WID_TF_BACKGROUND)->current_x - WidgetDimensions::scaled.frametext.Horizontal();
|
||||||
|
|
||||||
|
bool wrapped = this->IsTextWrapped();
|
||||||
|
bool dirty = false;
|
||||||
|
int pos = this->vscroll->GetPosition();
|
||||||
|
|
||||||
|
for (/* nothing */; this->reflow_iter != this->reflow_end; ++this->reflow_iter) {
|
||||||
|
auto it = this->reflow_iter.Base();
|
||||||
|
Line &line = *it;
|
||||||
|
|
||||||
|
int old_lines = line.num_lines;
|
||||||
|
if (wrapped) {
|
||||||
|
if (line.wrapped_width != window_width) {
|
||||||
|
line.num_lines = GetStringHeight(line.text, window_width, FS_MONO) / GetCharacterHeight(FS_MONO);
|
||||||
|
line.wrapped_width = window_width;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line.max_width == -1) {
|
||||||
|
line.max_width = GetStringBoundingBox(line.text, FS_MONO).width;
|
||||||
|
this->max_width = std::max(this->max_width, line.max_width);
|
||||||
|
}
|
||||||
|
line.num_lines = 1;
|
||||||
|
line.wrapped_width = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust the total number of lines. */
|
||||||
|
this->num_lines += (line.num_lines - old_lines);
|
||||||
|
|
||||||
|
/* Maintain scroll position. */
|
||||||
|
if (this->visible_first > it) pos += (line.num_lines - old_lines);
|
||||||
|
|
||||||
|
/* Mark dirty if visible range is touched. */
|
||||||
|
if (it >= this->visible_first && it <= this->visible_last) dirty = true;
|
||||||
|
|
||||||
|
if (!CanContinueRealtimeTick()) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->vscroll->SetPosition(pos)) dirty = true;
|
||||||
|
|
||||||
|
return dirty ? ReflowState::VisibleReflowed : ReflowState::Reflowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextfileWindow::OnRealtimeTick(uint)
|
||||||
|
{
|
||||||
|
auto r = this->ContinueReflow();
|
||||||
|
if (r == ReflowState::None) return;
|
||||||
|
|
||||||
|
this->SetupScrollbars();
|
||||||
|
|
||||||
|
if (r == ReflowState::VisibleReflowed) {
|
||||||
|
this->SetWidgetDirty(WID_TF_BACKGROUND);
|
||||||
|
this->UpdateVisibleIterators();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caption is always dirty. */
|
||||||
|
this->SetWidgetDirty(WID_TF_CAPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextfileWindow::UpdateVisibleIterators()
|
||||||
|
{
|
||||||
|
int pos = this->vscroll->GetPosition();
|
||||||
|
int cap = this->vscroll->GetCapacity();
|
||||||
|
this->visible_first = this->GetIteratorFromPosition(pos);
|
||||||
|
|
||||||
|
/* The last visible iterator ignores line wrapping so that it does not need to change when line heights change. */
|
||||||
|
this->visible_last = std::ranges::next(this->visible_first, cap + 1, std::end(this->lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextfileWindow::OnScrollbarScroll(WidgetID widget)
|
||||||
|
{
|
||||||
|
if (widget != WID_TF_VSCROLLBAR) return;
|
||||||
|
|
||||||
|
this->UpdateVisibleIterators();
|
||||||
|
this->ReflowContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TextfileWindow::Line>::iterator TextfileWindow::GetIteratorFromPosition(int pos)
|
||||||
|
{
|
||||||
|
for (auto it = std::begin(this->lines); it != std::end(this->lines); ++it) {
|
||||||
|
pos -= it->num_lines;
|
||||||
|
if (pos <= 0) return it;
|
||||||
|
}
|
||||||
|
return std::end(this->lines);
|
||||||
|
}
|
||||||
|
|
||||||
void TextfileWindow::ScrollToLine(size_t line)
|
void TextfileWindow::ScrollToLine(size_t line)
|
||||||
{
|
{
|
||||||
Scrollbar *sb = this->GetScrollbar(WID_TF_VSCROLLBAR);
|
Scrollbar *sb = this->GetScrollbar(WID_TF_VSCROLLBAR);
|
||||||
int newpos;
|
int newpos = 0;
|
||||||
if (this->IsTextWrapped()) {
|
for (auto it = std::begin(this->lines); it != std::end(this->lines) && line > 0; --line, ++it) {
|
||||||
newpos = this->lines[line].top;
|
newpos += it->num_lines;
|
||||||
} else {
|
|
||||||
newpos = static_cast<int>(line);
|
|
||||||
}
|
}
|
||||||
sb->SetPosition(std::min(newpos, sb->GetCount() - sb->GetCapacity()));
|
sb->SetPosition(std::min(newpos, sb->GetCount() - sb->GetCapacity()));
|
||||||
|
this->UpdateVisibleIterators();
|
||||||
|
this->ReflowContent();
|
||||||
this->SetDirty();
|
this->SetDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -814,25 +911,17 @@ void TextfileWindow::LoadText(std::string_view buf)
|
||||||
|
|
||||||
/* Split the string on newlines. */
|
/* Split the string on newlines. */
|
||||||
std::string_view p(text);
|
std::string_view p(text);
|
||||||
int row = 0;
|
|
||||||
auto next = p.find_first_of('\n');
|
auto next = p.find_first_of('\n');
|
||||||
while (next != std::string_view::npos) {
|
while (next != std::string_view::npos) {
|
||||||
this->lines.emplace_back(row, p.substr(0, next));
|
this->lines.emplace_back(p.substr(0, next));
|
||||||
p.remove_prefix(next + 1);
|
p.remove_prefix(next + 1);
|
||||||
|
|
||||||
row++;
|
|
||||||
next = p.find_first_of('\n');
|
next = p.find_first_of('\n');
|
||||||
}
|
}
|
||||||
this->lines.emplace_back(row, p);
|
this->lines.emplace_back(p);
|
||||||
|
|
||||||
/* Calculate maximum text line length. */
|
|
||||||
uint max_length = 0;
|
|
||||||
for (auto &line : this->lines) {
|
|
||||||
max_length = std::max(max_length, GetStringBoundingBox(line.text, FS_MONO).width);
|
|
||||||
}
|
|
||||||
this->max_length = max_length;
|
|
||||||
|
|
||||||
this->AfterLoadText();
|
this->AfterLoadText();
|
||||||
|
this->ReflowContent();
|
||||||
|
|
||||||
CheckForMissingGlyphs(true, this);
|
CheckForMissingGlyphs(true, this);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#ifndef TEXTFILE_GUI_H
|
#ifndef TEXTFILE_GUI_H
|
||||||
#define TEXTFILE_GUI_H
|
#define TEXTFILE_GUI_H
|
||||||
|
|
||||||
|
#include "misc/alternating_iterator.hpp"
|
||||||
#include "fileio_type.h"
|
#include "fileio_type.h"
|
||||||
#include "strings_func.h"
|
#include "strings_func.h"
|
||||||
#include "textfile_type.h"
|
#include "textfile_type.h"
|
||||||
|
@ -28,8 +29,11 @@ struct TextfileWindow : public Window, MissingGlyphSearcher {
|
||||||
bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override;
|
bool OnTooltip([[maybe_unused]] Point pt, WidgetID widget, TooltipCloseCondition close_cond) override;
|
||||||
void DrawWidget(const Rect &r, WidgetID widget) const override;
|
void DrawWidget(const Rect &r, WidgetID widget) const override;
|
||||||
void OnResize() override;
|
void OnResize() override;
|
||||||
|
void OnInit() override;
|
||||||
void OnInvalidateData(int data = 0, bool gui_scope = true) override;
|
void OnInvalidateData(int data = 0, bool gui_scope = true) override;
|
||||||
void OnDropdownSelect(WidgetID widget, int index) override;
|
void OnDropdownSelect(WidgetID widget, int index) override;
|
||||||
|
void OnRealtimeTick(uint delta_ms) override;
|
||||||
|
void OnScrollbarScroll(WidgetID widget) override;
|
||||||
|
|
||||||
void Reset() override;
|
void Reset() override;
|
||||||
FontSize DefaultSize() override;
|
FontSize DefaultSize() override;
|
||||||
|
@ -46,12 +50,13 @@ protected:
|
||||||
void ConstructWindow();
|
void ConstructWindow();
|
||||||
|
|
||||||
struct Line {
|
struct Line {
|
||||||
int top = 0; ///< Top scroll position in visual lines.
|
int num_lines = 1; ///< Number of visual lines for this line.
|
||||||
int bottom = 0; ///< Bottom scroll position in visual lines.
|
int wrapped_width = 0;
|
||||||
std::string text{}; ///< Contents of the line.
|
int max_width = -1;
|
||||||
TextColour colour = TC_WHITE; ///< Colour to render text line in.
|
TextColour colour = TC_WHITE; ///< Colour to render text line in.
|
||||||
|
std::string text{}; ///< Contents of the line.
|
||||||
|
|
||||||
Line(int top, std::string_view text) : top(top), bottom(top + 1), text(text) {}
|
Line(std::string_view text) : text(text) {}
|
||||||
Line() {}
|
Line() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,11 +105,29 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint search_iterator = 0; ///< Iterator for the font check search.
|
uint search_iterator = 0; ///< Iterator for the font check search.
|
||||||
uint max_length = 0; ///< Maximum length of unwrapped text line.
|
int max_width = 0; ///< Maximum length of unwrapped text line.
|
||||||
|
size_t num_lines = 0; ///< Number of lines of text, taking account of wrapping.
|
||||||
|
|
||||||
uint ReflowContent();
|
using LineIterator = std::vector<Line>::iterator;
|
||||||
uint GetContentHeight();
|
using ReflowIterator = AlternatingIterator<LineIterator>;
|
||||||
void SetupScrollbars(bool force_reflow);
|
|
||||||
|
ReflowIterator reflow_iter; ///< Current iterator for reflow.
|
||||||
|
ReflowIterator reflow_end; ///< End iterator for reflow.
|
||||||
|
|
||||||
|
LineIterator visible_first; ///< Iterator to first visible element.
|
||||||
|
LineIterator visible_last; ///< Iterator to last visible element.
|
||||||
|
|
||||||
|
enum class ReflowState : uint8_t {
|
||||||
|
None, ///< Nothing has been reflowed.
|
||||||
|
Reflowed, ///< Content has been reflowed.
|
||||||
|
VisibleReflowed, ///< Visible content has been reflowed.
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TextfileWindow::Line>::iterator GetIteratorFromPosition(int pos);
|
||||||
|
void UpdateVisibleIterators();
|
||||||
|
void ReflowContent();
|
||||||
|
ReflowState ContinueReflow();
|
||||||
|
void SetupScrollbars();
|
||||||
const Hyperlink *GetHyperlink(Point pt) const;
|
const Hyperlink *GetHyperlink(Point pt) const;
|
||||||
|
|
||||||
void AfterLoadMarkdown();
|
void AfterLoadMarkdown();
|
||||||
|
|
|
@ -3042,11 +3042,20 @@ void InputLoop()
|
||||||
HandleMouseEvents();
|
HandleMouseEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::chrono::time_point<std::chrono::steady_clock> _realtime_tick_start;
|
||||||
|
|
||||||
|
bool CanContinueRealtimeTick()
|
||||||
|
{
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
return std::chrono::duration_cast<std::chrono::milliseconds>(now - _realtime_tick_start).count() < (MILLISECONDS_PER_TICK * 3 / 4);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch OnRealtimeTick event over all windows
|
* Dispatch OnRealtimeTick event over all windows
|
||||||
*/
|
*/
|
||||||
void CallWindowRealtimeTickEvent(uint delta_ms)
|
void CallWindowRealtimeTickEvent(uint delta_ms)
|
||||||
{
|
{
|
||||||
|
_realtime_tick_start = std::chrono::steady_clock::now();
|
||||||
for (Window *w : Window::Iterate()) {
|
for (Window *w : Window::Iterate()) {
|
||||||
w->OnRealtimeTick(delta_ms);
|
w->OnRealtimeTick(delta_ms);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue