From 2233295cfd87ce764fd95f84711819dda2b80bac Mon Sep 17 00:00:00 2001 From: twanvl Date: Sat, 23 Dec 2006 12:47:08 +0000 Subject: [PATCH] scrollbar in text editor git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@154 0fc631ac-6414-0410-93d0-97cfa31319b6 --- src/gui/control/text_ctrl.cpp | 22 ++++--- src/gui/value/choice.cpp | 2 +- src/gui/value/choice.hpp | 2 +- src/gui/value/color.cpp | 2 +- src/gui/value/color.hpp | 2 +- src/gui/value/editor.hpp | 2 +- src/gui/value/symbol.cpp | 2 +- src/gui/value/symbol.hpp | 2 +- src/gui/value/text.cpp | 120 ++++++++++++++++++---------------- src/gui/value/text.hpp | 11 +++- src/render/text/viewer.cpp | 60 +++++++++++++++++ src/render/text/viewer.hpp | 25 +++++++ src/script/scriptable.hpp | 1 + 13 files changed, 179 insertions(+), 74 deletions(-) diff --git a/src/gui/control/text_ctrl.cpp b/src/gui/control/text_ctrl.cpp index b6d0d41e..c9151756 100644 --- a/src/gui/control/text_ctrl.cpp +++ b/src/gui/control/text_ctrl.cpp @@ -26,12 +26,6 @@ Rotation TextCtrl::getRotation() const { } void TextCtrl::draw(DC& dc) { - if (!viewers.empty()) { - wxSize cs = GetClientSize(); - Style& style = *viewers.front()->getStyle(); - style.width = cs.GetWidth() - 2; - style.height = cs.GetHeight() - 2; - } RotatedDC rdc(dc, getRotation(), false); DataViewer::draw(rdc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); } @@ -60,8 +54,11 @@ void TextCtrl::setValue(String* value) { IndexMap values; values.add(field, value); setStyles(set->stylesheet, styles); setData(values); - // determine required height - viewers.front()->getEditor()->determineSize(); + // determine size + wxSize cs = GetClientSize(); + style->width = cs.GetWidth() - 2; + style->height = cs.GetHeight() - 2; + viewers.front()->getEditor()->determineSize(true); SetMinSize(wxSize(style->width + 6, style->height + 6)); } valueChanged(); @@ -97,7 +94,14 @@ void TextCtrl::onInit() { } void TextCtrl::onSize(wxSizeEvent&) { - Refresh(false); + if (!viewers.empty()) { + wxSize cs = GetClientSize(); + Style& style = *viewers.front()->getStyle(); + style.width = cs.GetWidth() - 2; + style.height = cs.GetHeight() - 2; + viewers.front()->getEditor()->determineSize(true); + } + onChange(); } BEGIN_EVENT_TABLE(TextCtrl, DataEditor) diff --git a/src/gui/value/choice.cpp b/src/gui/value/choice.cpp index b5bb6dbd..3d1af626 100644 --- a/src/gui/value/choice.cpp +++ b/src/gui/value/choice.cpp @@ -108,7 +108,7 @@ void ChoiceValueEditor::draw(RotatedDC& dc) { draw_drop_down_arrow(&editor(), dc.getDC(), style().getRect().grow(1), drop_down->IsShown()); } } -void ChoiceValueEditor::determineSize() { +void ChoiceValueEditor::determineSize(bool) { style().height = max(style().height(), 16.); } diff --git a/src/gui/value/choice.hpp b/src/gui/value/choice.hpp index 89557532..5ffdcdb6 100644 --- a/src/gui/value/choice.hpp +++ b/src/gui/value/choice.hpp @@ -29,7 +29,7 @@ class ChoiceValueEditor : public ChoiceValueViewer, public ValueEditor { virtual void onLoseFocus(); virtual void draw(RotatedDC& dc); - virtual void determineSize(); + virtual void determineSize(bool); private: DropDownListP drop_down; diff --git a/src/gui/value/color.cpp b/src/gui/value/color.cpp index 51b89d49..64a7d01a 100644 --- a/src/gui/value/color.cpp +++ b/src/gui/value/color.cpp @@ -144,7 +144,7 @@ void ColorValueEditor::draw(RotatedDC& dc) { draw_drop_down_arrow(&editor(), dc.getDC(), style().getRect().grow(1), drop_down->IsShown()); } } -void ColorValueEditor::determineSize() { +void ColorValueEditor::determineSize(bool) { style().height = 20; } diff --git a/src/gui/value/color.hpp b/src/gui/value/color.hpp index dfb03431..b82af032 100644 --- a/src/gui/value/color.hpp +++ b/src/gui/value/color.hpp @@ -28,7 +28,7 @@ class ColorValueEditor : public ColorValueViewer, public ValueEditor { virtual void onLoseFocus(); virtual void draw(RotatedDC& dc); - virtual void determineSize(); + virtual void determineSize(bool); private: DropDownListP drop_down; diff --git a/src/gui/value/editor.hpp b/src/gui/value/editor.hpp index e30cb263..3b224434 100644 --- a/src/gui/value/editor.hpp +++ b/src/gui/value/editor.hpp @@ -96,7 +96,7 @@ class ValueEditor { /// The cursor type to use when the mouse is over this control virtual wxCursor cursor() const { return wxCursor(); } /// determines prefered size in the native look, update the style - virtual void determineSize() {} + virtual void determineSize(bool force_fit = false) {} /// The editor is shown or hidden virtual void onShow(bool) {} }; diff --git a/src/gui/value/symbol.cpp b/src/gui/value/symbol.cpp index 00aeed0e..7ad0ef14 100644 --- a/src/gui/value/symbol.cpp +++ b/src/gui/value/symbol.cpp @@ -19,6 +19,6 @@ void SymbolValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent&) { wnd->Show(); } -void SymbolValueEditor::determineSize() { +void SymbolValueEditor::determineSize(bool) { style().height = 50; } diff --git a/src/gui/value/symbol.hpp b/src/gui/value/symbol.hpp index 566dacde..3dfac7fc 100644 --- a/src/gui/value/symbol.hpp +++ b/src/gui/value/symbol.hpp @@ -21,7 +21,7 @@ class SymbolValueEditor : public SymbolValueViewer, public ValueEditor { DECLARE_VALUE_EDITOR(Symbol); virtual void onLeftDClick(const RealPoint& pos, wxMouseEvent&); - virtual void determineSize(); + virtual void determineSize(bool); }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/gui/value/text.cpp b/src/gui/value/text.cpp index ee668f0b..c89fca97 100644 --- a/src/gui/value/text.cpp +++ b/src/gui/value/text.cpp @@ -29,9 +29,9 @@ class TextValueEditorScrollBar : public wxWindow { }; -TextValueEditorScrollBar::TextValueEditorScrollBar(TextValueEditor& te) +TextValueEditorScrollBar::TextValueEditorScrollBar(TextValueEditor& tve) : wxWindow(&tve.editor(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxVSCROLL | wxALWAYS_SHOW_SB) - , tve(te) + , tve(tve) {} void TextValueEditorScrollBar::onScroll(wxScrollWinEvent& ev) { @@ -57,8 +57,12 @@ IMPLEMENT_VALUE_EDITOR(Text) , selection_start (0), selection_end (0) , selection_start_i(0), selection_end_i(0) , select_words(false) - , scrollbar(nullptr) -{} + , scrollbar(nullptr), scroll_with_cursor(false) +{ + if (viewer.nativeLook() && field().multi_line) { + scrollbar = new TextValueEditorScrollBar(*this); + } +} TextValueEditor::~TextValueEditor() { delete scrollbar; @@ -234,10 +238,13 @@ void TextValueEditor::onMenu(wxCommandEvent& ev) { // ----------------------------------------------------------------------------- : Other overrides void TextValueEditor::draw(RotatedDC& dc) { + // update scrollbar + prepareDrawScrollbar(dc); + // draw text TextValueViewer::draw(dc); + // draw selection if (isCurrent()) { v.drawSelection(dc, style(), selection_start_i, selection_end_i); - // show caret, onAction() would be a better place // but it has to be done after the viewer has updated the TextViewer // we could do that ourselfs, but we need a dc for that @@ -469,7 +476,7 @@ void TextValueEditor::replaceSelection(const String& replacement, const String& } fixSelection(TYPE_INDEX, MOVE_MID); // scroll with next update -// scrollWithCursor = true; + scroll_with_cursor = true; } void TextValueEditor::moveSelection(IndexType t, size_t new_end, bool also_move_start, Movement dir) { @@ -495,15 +502,15 @@ void TextValueEditor::moveSelection(IndexType t, size_t new_end, bool also_move_ // move moveSelectionNoRedraw(t, new_end, also_move_start, dir); // scroll? - // scrollWithCursor = true; - // if (onMove()) { - // // we can't redraw just the selection because we must scroll - // updateScrollbar(); - // editor.refreshEditor(); - // } else { + scroll_with_cursor = true; + if (ensureCaretVisible()) { + // we can't redraw just the selection because we must scroll + updateScrollbar(); +// editor.refreshEditor(); + } else { // draw new selection v.drawSelection(rdc, style(), selection_start_i, selection_end_i); - // } + } } showCaret(); } @@ -545,46 +552,6 @@ void TextValueEditor::fixSelection(IndexType t, Movement dir) { // find next separator seppos = val.find(_(" seppos) selection_end = seppos; // not on same side - if (selection_start >= sepend && selection_end < sepend) selection_end = sepend; // not on same side - if (selection_start > seppos && selection_start < sepend) { - // start inside separator - selection_start = move(selection_start, seppos, sepend, dir); - } - if (selection_end > seppos && selection_end < sepend) { - // end inside separator - selection_end = selection_start < sepend ? seppos : sepend; - } - // find next separator - seppos = val.find(_("? if so, move them out - size_t atompos = val.find(_(" atompos && selection_start < atomend) { // start inside atom - selection_start = move(selection_start, atompos, atomend, dir); - } - if (selection_end > atompos && selection_end < atomend) { // end inside atom - selection_end = move(selection_end, atompos, atomend, dir); - } - // find next atom - atompos = val.find(_("SetSize( style().left + style().width - sbw + 1, @@ -667,8 +634,47 @@ void TextValueEditor::onMouseWheel(const RealPoint& pos, wxMouseEvent& ev) { void TextValueEditor::scrollTo(int pos) { // scroll -// r.scrollTo(pos); + v.scrollTo(pos); // move the cursor if needed // refresh -// editor.refreshEditor(); +// viewer.onChange(); +} + +bool TextValueEditor::ensureCaretVisible() { + if (scrollbar && scroll_with_cursor) { + scroll_with_cursor = false; + return v.ensureVisible(style().height - style().padding_top - style().padding_bottom, selection_end_i); + } + return false; +} + +void TextValueEditor::updateScrollbar() { + assert(scrollbar); + int position = (int)v.firstVisibleLine(); + int page_size = (int)v.visibleLineCount(style().height - style().padding_top - style().padding_bottom); + int range = (int)v.lineCount(); + scrollbar->SetScrollbar( + wxVERTICAL, + position, + page_size, + range, + page_size > 1 ? page_size - 1 : 0 + ); +} + +void TextValueEditor::prepareDrawScrollbar(RotatedDC& dc) { + if (scrollbar) { + // don't draw under the scrollbar + int scrollbar_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X); + style().width.mutate() -= scrollbar_width; + // prepare text, and remember scroll position + double scroll_pos = v.getExactScrollPosition(); + v.prepare(dc, value().value(), style(), viewer.getContext()); + v.setExactScrollPosition(scroll_pos); + // scroll to the same place, but always show the caret + ensureCaretVisible(); + // update after scrolling + updateScrollbar(); + style().width.mutate() += scrollbar_width; + } } \ No newline at end of file diff --git a/src/gui/value/text.hpp b/src/gui/value/text.hpp index 7e29ee21..d9a02a73 100644 --- a/src/gui/value/text.hpp +++ b/src/gui/value/text.hpp @@ -76,7 +76,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { // --------------------------------------------------- : Other virtual wxCursor cursor() const; - virtual void determineSize(); + virtual void determineSize(bool force_fit = false); virtual void onShow(bool); virtual void draw(RotatedDC&); @@ -127,8 +127,17 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { friend class TextValueEditorScrollBar; + /// When the cursor moves, should the scrollposition change? + bool scroll_with_cursor; + /// Scroll to the given position, called by scrollbar void scrollTo(int pos); + /// Update the scrollbar to show the current scroll position + void updateScrollbar(); + /// Scrolls to ensure the caret stays visible, return true if the control is scrolled + bool ensureCaretVisible(); + /// Prepare for drawing if there is a scrollbar + void prepareDrawScrollbar(RotatedDC& dc); }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/render/text/viewer.cpp b/src/render/text/viewer.cpp index 0795da89..0aafbae0 100644 --- a/src/render/text/viewer.cpp +++ b/src/render/text/viewer.cpp @@ -229,6 +229,66 @@ double TextViewer::heightOfLastLine() const { return lines.back().line_height; } +// ----------------------------------------------------------------------------- : Scrolling + +size_t TextViewer::lineCount() const { + return lines.size(); +} +size_t TextViewer::visibleLineCount(double height) const { + size_t count = 0; + FOR_EACH_CONST(l, lines) { + if (l.top + l.line_height > height) return count; + if (l.top >= 0) ++count; + } + return count; +} +size_t TextViewer::firstVisibleLine() const { + size_t i = 0; + FOR_EACH_CONST(l, lines) { + if (l.top >= 0) return i; + i++; + } + return 0; //no visible lines +} + +void TextViewer::scrollTo(size_t line_id) { + scrollBy(-lines.at(line_id).top); +} +void TextViewer::scrollBy(double delta) { + if (delta == 0) return; + FOR_EACH(l, lines) { + l.top += delta; + } +} + +bool TextViewer::ensureVisible(double height, size_t char_id) { + const Line& line = findLine(char_id); + if (line.top < 0) { + // scroll up + scrollBy(-line.top); + return true; + } else if (line.bottom() > height) { + // scroll down + FOR_EACH(l, lines) { + if (l.top > 0) scrollBy(-l.line_height); // scroll down a single line ... + if (line.bottom() <= height) break; // ... until we can see the current line + } + return true; + } else { + return false; // line was already visible + } +} + +double TextViewer::getExactScrollPosition() const { + if (lines.empty()) return 0; + return -lines.front().top; +} +void TextViewer::setExactScrollPosition(double pos) { + if (lines.empty()) return; // no scrolling is needed + pos += lines.front().top; + scrollBy(-pos); +} + // ----------------------------------------------------------------------------- : Elements void TextViewer::prepareElements(const String& text, const TextStyle& style, Context& ctx) { diff --git a/src/render/text/viewer.hpp b/src/render/text/viewer.hpp index 8070a0f4..2ba19ba2 100644 --- a/src/render/text/viewer.hpp +++ b/src/render/text/viewer.hpp @@ -90,6 +90,31 @@ class TextViewer { /// Return the height of the last line double heightOfLastLine() const; + // --------------------------------------------------- : Lines/scrolling + + /// The total number of lines + size_t lineCount() const; + /// number of fully visible lines, height gives the height of the box + size_t visibleLineCount(double height) const; + /// the index of the first visible line + size_t firstVisibleLine() const; + // scroll so line_id becomes the first visible line + void scrollTo(size_t line_id); + /// Ensure the specified character is fully visible + /* Always scrolls by a whole line. + * Returns true if the editor has scrolled. + */ + bool ensureVisible(double height, size_t char_id); + + /// Get exact scroll position + double getExactScrollPosition() const; + /// Set exact scroll position + void setExactScrollPosition(double pos); + + private: + /// Scroll all lines a given amount + void scrollBy(double delta); + private: // --------------------------------------------------- : More drawing double scale; /// < Scale when drawing diff --git a/src/script/scriptable.hpp b/src/script/scriptable.hpp index 48fb764f..4f38c559 100644 --- a/src/script/scriptable.hpp +++ b/src/script/scriptable.hpp @@ -111,6 +111,7 @@ class Scriptable { inline operator const T& () const { return value; } inline const T& operator ()() const { return value; } + inline T& mutate () { return value; } inline bool isScripted() const { return script; } /// Has this value been read from a Reader? inline bool hasBeenRead() const { return !script.unparsed.empty(); }