mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
Fixed some conflicts
This commit is contained in:
@@ -4,6 +4,7 @@ escher_src += $(addprefix escher/src/,\
|
||||
alternate_empty_view_controller.cpp \
|
||||
app.cpp \
|
||||
bank_view_controller.cpp \
|
||||
bordered.cpp \
|
||||
buffer_text_view.cpp \
|
||||
button.cpp \
|
||||
button_row_controller.cpp \
|
||||
@@ -44,7 +45,6 @@ escher_src += $(addprefix escher/src/,\
|
||||
message_table_cell_with_message.cpp \
|
||||
message_table_cell_with_switch.cpp \
|
||||
message_text_view.cpp \
|
||||
message_tree.cpp \
|
||||
modal_view_controller.cpp \
|
||||
nested_menu_controller.cpp \
|
||||
palette.cpp \
|
||||
@@ -54,13 +54,12 @@ escher_src += $(addprefix escher/src/,\
|
||||
scroll_view.cpp \
|
||||
scroll_view_data_source.cpp \
|
||||
scroll_view_indicator.cpp \
|
||||
scrollable_expression_view.cpp \
|
||||
scrollable_view.cpp \
|
||||
selectable_table_view.cpp \
|
||||
selectable_table_view_delegate.cpp \
|
||||
simple_list_view_data_source.cpp \
|
||||
simple_table_view_data_source.cpp \
|
||||
solid_color_view.cpp \
|
||||
solid_text_area.cpp \
|
||||
stack_view.cpp \
|
||||
stack_view_controller.cpp \
|
||||
switch_view.cpp \
|
||||
@@ -87,6 +86,9 @@ escher_src += $(addprefix escher/src/,\
|
||||
window.cpp \
|
||||
)
|
||||
|
||||
tests_src += $(addprefix escher/test/,\
|
||||
layout_field.cpp\
|
||||
)
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
HOSTCC, \
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include <escher/scroll_view_data_source.h>
|
||||
#include <escher/scroll_view_indicator.h>
|
||||
#include <escher/scrollable_view.h>
|
||||
#include <escher/scrollable_expression_view.h>
|
||||
#include <escher/selectable_table_view.h>
|
||||
#include <escher/selectable_table_view_data_source.h>
|
||||
#include <escher/selectable_table_view_delegate.h>
|
||||
|
||||
@@ -21,7 +21,7 @@ private:
|
||||
ContentView(ViewController * mainViewController, AlternateEmptyViewDelegate * delegate);
|
||||
ViewController * mainViewController() const;
|
||||
AlternateEmptyViewDelegate * alternateEmptyViewDelegate() const;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
private:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <escher/view_controller.h>
|
||||
#include <escher/warning_controller.h>
|
||||
#include <ion/storage.h>
|
||||
#include <poincare/context.h>
|
||||
|
||||
/* An app is fed events and outputs drawing calls.
|
||||
*
|
||||
@@ -25,10 +26,10 @@ class App : public Responder {
|
||||
public:
|
||||
class Descriptor {
|
||||
public:
|
||||
virtual I18n::Message name();
|
||||
virtual I18n::Message upperName();
|
||||
virtual I18n::Message name() { return (I18n::Message)0; }
|
||||
virtual I18n::Message upperName() { return (I18n::Message)0; }
|
||||
virtual int examinationLevel();
|
||||
virtual const Image * icon();
|
||||
virtual const Image * icon() { return nullptr; }
|
||||
|
||||
const int NoExaminationLevel = 0;
|
||||
const int BasicExaminationLevel = 1;
|
||||
@@ -39,37 +40,44 @@ public:
|
||||
virtual App * unpack(Container * container) = 0;
|
||||
void pack(App * app);
|
||||
/* reset all instances to their initial values */
|
||||
virtual void reset();
|
||||
virtual void reset() {}
|
||||
virtual void storageDidChangeForRecord(Ion::Storage::Record) {}
|
||||
virtual Descriptor * descriptor() = 0;
|
||||
#if EPSILON_GETOPT
|
||||
virtual void setOpt(const char * name, const char * value) {}
|
||||
#endif
|
||||
/* tidy clean all dynamically-allocated data */
|
||||
virtual void tidy();
|
||||
virtual void tidy() {}
|
||||
};
|
||||
/* The destructor has to be virtual. Otherwise calling a destructor on an
|
||||
* App * pointing to a Derived App would have undefined behaviour. */
|
||||
virtual ~App() = default;
|
||||
Snapshot * snapshot() const { return m_snapshot; }
|
||||
void setFirstResponder(Responder * responder);
|
||||
Responder * firstResponder();
|
||||
Responder * firstResponder() { return m_firstResponder; }
|
||||
virtual bool processEvent(Ion::Events::Event event);
|
||||
/* prepareForExit returns true if the app can be switched off in the current
|
||||
* runloop step, else it prepares for a switch off and returns false. */
|
||||
virtual bool prepareForExit() { return true; }
|
||||
void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment,
|
||||
KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0);
|
||||
void dismissModalViewController();
|
||||
void dismissModalViewController(bool willExitApp = false);
|
||||
void displayWarning(I18n::Message warningMessage1, I18n::Message warningMessage2 = (I18n::Message) 0, bool specialExitKeys = false);
|
||||
|
||||
virtual void didBecomeActive(Window * window);
|
||||
virtual void willBecomeInactive();
|
||||
View * modalView();
|
||||
virtual int numberOfTimers();
|
||||
virtual Timer * timerAtIndex(int i);
|
||||
virtual int numberOfTimers() { return 0; }
|
||||
virtual Timer * timerAtIndex(int i) { assert(false); return nullptr; }
|
||||
virtual Poincare::Context * localContext() { return nullptr; }
|
||||
protected:
|
||||
App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0);
|
||||
App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0) :
|
||||
Responder(nullptr),
|
||||
m_modalViewController(this, rootViewController),
|
||||
m_firstResponder(nullptr),
|
||||
m_snapshot(snapshot),
|
||||
m_warningController(this, warningMessage)
|
||||
{}
|
||||
ModalViewController m_modalViewController;
|
||||
private:
|
||||
Responder * m_firstResponder;
|
||||
|
||||
@@ -31,8 +31,8 @@ private:
|
||||
assert(index == 0);
|
||||
return m_subview;
|
||||
}
|
||||
void layoutSubviews() override {
|
||||
m_subview->setFrame(bounds());
|
||||
void layoutSubviews(bool force = false) override {
|
||||
m_subview->setFrame(bounds(), force);
|
||||
}
|
||||
View * m_subview;
|
||||
};
|
||||
|
||||
16
escher/include/escher/bordered.h
Normal file
16
escher/include/escher/bordered.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef ESCHER_BORDERED_H
|
||||
#define ESCHER_BORDERED_H
|
||||
|
||||
#include <escher/metric.h>
|
||||
#include <kandinsky/context.h>
|
||||
|
||||
class Bordered {
|
||||
public:
|
||||
void drawBorderOfRect(KDContext * ctx, KDRect rect, KDColor borderColor) const;
|
||||
void drawInnerRect(KDContext * ctx, KDRect rect, KDColor backgroundColor) const;
|
||||
protected:
|
||||
constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,7 @@ private:
|
||||
constexpr static KDCoordinate k_horizontalMarginLarge = 20;
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
Invocation m_invocation;
|
||||
const KDFont * m_font;
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ private:
|
||||
void reload();
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
bool setSelectedButton(int selectedButton);
|
||||
int selectedButton() const { return m_selectedButton; }
|
||||
@@ -57,8 +57,6 @@ private:
|
||||
constexpr static KDCoordinate k_embossedStyleHeightLarge = 52;
|
||||
constexpr static KDCoordinate k_embossedStyleHeightMarginSmall = 6;
|
||||
constexpr static KDCoordinate k_embossedStyleHeightMarginLarge = 8;
|
||||
constexpr static KDColor k_separatorHeaderColor = KDColor::RGB24(0xDEE0E2);
|
||||
constexpr static KDColor k_selectedBackgroundColor = KDColor::RGB24(0x426DA7);
|
||||
ViewController * m_mainViewController;
|
||||
int m_selectedButton;
|
||||
ButtonRowDelegate * m_delegate;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class Clipboard {
|
||||
public:
|
||||
static Clipboard * sharedClipboard();
|
||||
void store(const char * storedText);
|
||||
void store(const char * storedText, int length = -1);
|
||||
const char * storedText() const { return m_textBuffer; }
|
||||
void reset();
|
||||
private:
|
||||
|
||||
11
escher/include/escher/context_provider.h
Normal file
11
escher/include/escher/context_provider.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef ESCHER_CONTEXT_PROVIDER_H
|
||||
#define ESCHER_CONTEXT_PROVIDER_H
|
||||
|
||||
#include <poincare/context.h>
|
||||
|
||||
class ContextProvider {
|
||||
public:
|
||||
virtual Poincare::Context * context() const { return nullptr; }
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <escher/input_event_handler.h>
|
||||
#include <ion/unicode/code_point.h>
|
||||
|
||||
/* TODO: improve classes hierarchy to share selection handling (and some other
|
||||
* features) between EditableField and TextInput. Refactor the following classes:
|
||||
* InputEventHandler, TextInput, TextArea, EditableField, LayoutField,
|
||||
* TextField, and their delegates! */
|
||||
|
||||
class EditableField : public InputEventHandler {
|
||||
public:
|
||||
using InputEventHandler::InputEventHandler;
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
}
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
void didBecomeFirstResponder() override;
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
private:
|
||||
|
||||
@@ -26,7 +26,7 @@ protected:
|
||||
static constexpr KDCoordinate k_horizontalMargin = Metric::CellMargin;
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
BufferTextView m_bufferTextView;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ private:
|
||||
assert(index==0);
|
||||
return &m_ellipsisView;
|
||||
}
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
EllipsisView m_ellipsisView;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
private:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
EditableTextCell m_editableCell;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
protected:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
ExpressionView m_expressionView;
|
||||
KDCoordinate m_leftMargin;
|
||||
KDCoordinate m_rightMargin;
|
||||
|
||||
@@ -18,7 +18,7 @@ protected:
|
||||
constexpr static KDCoordinate k_horizontalMargin = Metric::CellMargin;
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
MessageTextView m_messageTextView;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,30 +21,31 @@ public:
|
||||
* use text() there... TODO: change text() for fillTextInBuffer?*/
|
||||
const char * text();
|
||||
void setText(const char * text);
|
||||
void reload();
|
||||
bool editionIsInTextField() const;
|
||||
bool isEmpty() const;
|
||||
bool heightIsMaximal() const;
|
||||
bool inputViewHeightDidChange();
|
||||
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false);
|
||||
|
||||
/* View */
|
||||
int numberOfSubviews() const override { return 1; }
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
|
||||
/* Responder */
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
void didBecomeFirstResponder() override;
|
||||
|
||||
private:
|
||||
static constexpr int k_textFieldBufferSize = TextField::maxBufferSize();
|
||||
static constexpr KDCoordinate k_textFieldHeight = 37;
|
||||
static constexpr KDCoordinate k_minimalHeight = 37;
|
||||
static constexpr KDCoordinate k_maximalHeight = 0.6*Ion::Display::Height;
|
||||
static constexpr KDCoordinate k_horizontalMargin = 5;
|
||||
static constexpr KDCoordinate k_verticalMargin = 5;
|
||||
constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness;
|
||||
KDCoordinate inputViewHeight() const;
|
||||
KDCoordinate maximalHeight() const;
|
||||
KDCoordinate m_inputViewMemoizedHeight;
|
||||
TextField m_textField;
|
||||
LayoutField m_layoutField;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
#ifndef ESCHER_EXPRESSION_TABLE_CELL_H
|
||||
#define ESCHER_EXPRESSION_TABLE_CELL_H
|
||||
|
||||
#include <escher/expression_view.h>
|
||||
#include <escher/scrollable_expression_view.h>
|
||||
#include <escher/table_cell.h>
|
||||
|
||||
class ExpressionTableCell : public TableCell {
|
||||
class ExpressionTableCell : public Responder, public TableCell {
|
||||
public:
|
||||
ExpressionTableCell(Layout layout = Layout::Horizontal);
|
||||
ExpressionTableCell(Responder * responder = nullptr, Layout layout = Layout::HorizontalRightOverlap);
|
||||
View * labelView() const override;
|
||||
void setHighlighted(bool highlight) override;
|
||||
void setLayout(Poincare::Layout layout);
|
||||
Poincare::Layout layout() const override { return m_labelExpressionView.layout(); }
|
||||
|
||||
Responder * responder() override {
|
||||
return this;
|
||||
}
|
||||
void didBecomeFirstResponder() override;
|
||||
virtual void reloadScroll() { m_labelExpressionView.reloadScroll(); }
|
||||
private:
|
||||
ExpressionView m_labelExpressionView;
|
||||
// Remove margins added by TableCell because they're already handled by ScrollableInputExactApproximateExpressionsView
|
||||
KDCoordinate labelMargin() const override { return 0; }
|
||||
ScrollableExpressionView m_labelExpressionView;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,12 +6,16 @@
|
||||
|
||||
class ExpressionTableCellWithExpression : public ExpressionTableCell {
|
||||
public:
|
||||
ExpressionTableCellWithExpression();
|
||||
ExpressionTableCellWithExpression(Responder * parentResponder = nullptr);
|
||||
View * accessoryView() const override;
|
||||
void setHighlighted(bool highlight) override;
|
||||
void setAccessoryLayout(Poincare::Layout l);
|
||||
void didBecomeFirstResponder() override;
|
||||
void reloadScroll() override { m_accessoryExpressionView.reloadScroll(); }
|
||||
private:
|
||||
ExpressionView m_accessoryExpressionView;
|
||||
// Accessory margin is already handled in ScrollableExpressionView
|
||||
KDCoordinate accessoryMargin() const override { return 0; }
|
||||
ScrollableExpressionView m_accessoryExpressionView;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
class ExpressionTableCellWithPointer : public ExpressionTableCell {
|
||||
public:
|
||||
ExpressionTableCellWithPointer(I18n::Message accessoryMessage = (I18n::Message)0, Layout layout = Layout::Horizontal);
|
||||
ExpressionTableCellWithPointer(Responder * responder = nullptr, I18n::Message accessoryMessage = (I18n::Message)0, Layout layout = Layout::HorizontalRightOverlap);
|
||||
View * accessoryView() const override;
|
||||
void setHighlighted(bool highlight) override;
|
||||
void setAccessoryMessage(I18n::Message messageBody);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
class ExpressionView : public View {
|
||||
public:
|
||||
ExpressionView(float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f,
|
||||
KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground);
|
||||
KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground, Poincare::Layout * selectionStart = nullptr, Poincare::Layout * selectionEnd = nullptr);
|
||||
Poincare::Layout layout() const { return m_layout; }
|
||||
bool setLayout(Poincare::Layout layout);
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
@@ -26,17 +26,20 @@ public:
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
KDPoint drawingOrigin() const;
|
||||
KDPoint absoluteDrawingOrigin() const;
|
||||
private:
|
||||
protected:
|
||||
/* Warning: we do not need to delete the previous expression layout when
|
||||
* deleting object or setting a new expression layout. Indeed, the expression
|
||||
* layout is always possessed by a controller which only gives a pointer to
|
||||
* the expression view (without cloning it). The named controller is then
|
||||
* responsible for freeing the expression layout when required. */
|
||||
mutable Poincare::Layout m_layout; // TODO find better way to have minimalSizeForOptimalDisplay const
|
||||
float m_horizontalAlignment;
|
||||
float m_verticalAlignment;
|
||||
KDColor m_textColor;
|
||||
KDColor m_backgroundColor;
|
||||
Poincare::Layout * m_selectionStart;
|
||||
Poincare::Layout * m_selectionEnd;
|
||||
private:
|
||||
float m_horizontalAlignment;
|
||||
float m_verticalAlignment;
|
||||
KDCoordinate m_horizontalMargin;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <ion/events.h>
|
||||
|
||||
// See TODO in EditableField
|
||||
|
||||
class InputEventHandlerDelegate;
|
||||
|
||||
class InputEventHandler {
|
||||
|
||||
@@ -64,7 +64,6 @@ private:
|
||||
InputEventHandlerDelegate * m_inputEventHandlerDelegate;
|
||||
TextFieldDelegate * m_textFieldDelegate;
|
||||
LayoutFieldDelegate * m_layoutFieldDelegate;
|
||||
bool m_inputViewHeightIsMaximal;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <poincare/layout.h>
|
||||
#include <poincare/layout_cursor.h>
|
||||
|
||||
// See TODO in EditableField
|
||||
|
||||
class LayoutField : public ScrollableView, public ScrollViewDataSource, public EditableField {
|
||||
public:
|
||||
LayoutField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate = nullptr) :
|
||||
@@ -20,15 +22,17 @@ public:
|
||||
m_delegate(delegate)
|
||||
{}
|
||||
void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; }
|
||||
Poincare::Context * context() const;
|
||||
bool isEditing() const override { return m_contentView.isEditing(); }
|
||||
void setEditing(bool isEditing) override;
|
||||
void clearLayout() { m_contentView.clearLayout(); }
|
||||
void scrollToCursor() {
|
||||
scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline());
|
||||
scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baselineWithoutSelection());
|
||||
}
|
||||
bool hasText() const { return layout().hasText(); }
|
||||
Poincare::Layout layout() const { return m_contentView.expressionView()->layout(); }
|
||||
CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override;
|
||||
void putCursorRightOfLayout();
|
||||
|
||||
// ScrollableView
|
||||
void setBackgroundColor(KDColor c) override {
|
||||
@@ -39,18 +43,20 @@ public:
|
||||
/* Responder */
|
||||
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override;
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
bool shouldFinishEditing(Ion::Events::Event event) override { // TODO REMOVE ?
|
||||
return m_delegate->layoutFieldShouldFinishEditing(this, event);
|
||||
}
|
||||
// TODO: factorize with TextField (see TODO of EditableField)
|
||||
bool shouldFinishEditing(Ion::Events::Event event) override;
|
||||
|
||||
protected:
|
||||
void reload(KDSize previousSize);
|
||||
virtual bool privateHandleEvent(Ion::Events::Event event);
|
||||
bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
|
||||
// Selection
|
||||
bool resetSelection() { return m_contentView.resetSelection(); }
|
||||
void deleteSelection();
|
||||
|
||||
private:
|
||||
constexpr static int k_maxNumberOfLayouts = 220;
|
||||
static_assert(k_maxNumberOfLayouts == TextField::maxBufferSize(), "Maximal number of layouts in a layout field should be equal to max number of char in text field");
|
||||
void reload(KDSize previousSize);
|
||||
virtual bool privateHandleEvent(Ion::Events::Event event);
|
||||
bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
|
||||
bool privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout);
|
||||
void scrollRightOfLayout(Poincare::Layout layoutR);
|
||||
void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline);
|
||||
void insertLayoutAtCursor(Poincare::Layout layoutR, Poincare::Expression correspondingExpression, bool forceCursorRightOfLayout = false);
|
||||
@@ -62,26 +68,35 @@ private:
|
||||
bool setEditing(bool isEditing); // returns True if LayoutField should reload
|
||||
void setBackgroundColor(KDColor c) { m_expressionView.setBackgroundColor(c); }
|
||||
void setCursor(Poincare::LayoutCursor cursor) { m_cursor = cursor; }
|
||||
void cursorPositionChanged() { layoutCursorSubview(); }
|
||||
void cursorPositionChanged() { layoutCursorSubview(false); }
|
||||
KDRect cursorRect() { return m_cursorView.frame(); }
|
||||
Poincare::LayoutCursor * cursor() { return &m_cursor; }
|
||||
const ExpressionView * expressionView() const { return &m_expressionView; }
|
||||
ExpressionView * editableExpressionView() { return &m_expressionView; }
|
||||
void clearLayout();
|
||||
/* View */
|
||||
// View
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
// Selection
|
||||
Poincare::Layout * selectionStart() { return &m_selectionStart; }
|
||||
Poincare::Layout * selectionEnd() { return &m_selectionEnd; }
|
||||
void addSelection(Poincare::Layout addedLayout);
|
||||
bool resetSelection(); // returns true if the selection was indeed reset
|
||||
void copySelection(Poincare::Context * context);
|
||||
bool selectionIsEmpty() const;
|
||||
void deleteSelection();
|
||||
|
||||
private:
|
||||
enum class Position {
|
||||
Top,
|
||||
Bottom
|
||||
};
|
||||
int numberOfSubviews() const override { return 2; }
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutCursorSubview();
|
||||
void layoutSubviews(bool force = false) override;
|
||||
void layoutCursorSubview(bool force);
|
||||
KDRect selectionRect() const;
|
||||
Poincare::LayoutCursor m_cursor;
|
||||
ExpressionView m_expressionView;
|
||||
TextCursorView m_cursorView;
|
||||
/* The selection starts on the left of m_selectionStart, and ends on the
|
||||
* right of m_selectionEnd. */
|
||||
Poincare::Layout m_selectionStart;
|
||||
Poincare::Layout m_selectionEnd;
|
||||
bool m_isEditing;
|
||||
};
|
||||
ContentView m_contentView;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#ifndef ESCHER_LAYOUT_FIELD_DELEGATE_H
|
||||
#define ESCHER_LAYOUT_FIELD_DELEGATE_H
|
||||
|
||||
#include <escher/context_provider.h>
|
||||
#include <ion/events.h>
|
||||
#include <poincare/layout.h>
|
||||
|
||||
class LayoutField;
|
||||
|
||||
class LayoutFieldDelegate {
|
||||
class LayoutFieldDelegate : public ContextProvider{
|
||||
public:
|
||||
virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0;
|
||||
virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
class MessageTableCell : public TableCell {
|
||||
public:
|
||||
MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::Horizontal);
|
||||
MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::HorizontalLeftOverlap);
|
||||
View * labelView() const override;
|
||||
virtual void setHighlighted(bool highlight) override;
|
||||
void setMessage(I18n::Message message);
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
void setAccessoryText(const char * text);
|
||||
void setTextColor(KDColor color) override;
|
||||
private:
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
TextField m_textField;
|
||||
char m_textBody[Poincare::PrintFloat::k_maxFloatCharSize];
|
||||
};
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
|
||||
class MessageTree {
|
||||
public:
|
||||
constexpr MessageTree(I18n::Message label = (I18n::Message)0, int numberOfChildren = 0) :
|
||||
constexpr MessageTree(I18n::Message label = (I18n::Message)0, const int numberOfChildren = 0) :
|
||||
m_label(label),
|
||||
m_numberOfChildren(numberOfChildren)
|
||||
{
|
||||
};
|
||||
virtual const MessageTree * children(int index) const = 0;
|
||||
I18n::Message label() const;
|
||||
int numberOfChildren() const;
|
||||
bool isNull() const;
|
||||
I18n::Message label() const { return m_label; }
|
||||
int numberOfChildren() const { return m_numberOfChildren; }
|
||||
bool isNull() const { return (m_label == (I18n::Message)0); }
|
||||
protected:
|
||||
I18n::Message m_label;
|
||||
int m_numberOfChildren;
|
||||
const int m_numberOfChildren;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,11 +17,12 @@ public:
|
||||
constexpr static KDCoordinate ParameterCellHeight = 35;
|
||||
constexpr static KDCoordinate ModalTopMargin = 5;
|
||||
constexpr static KDCoordinate ModalBottomMargin = 18;
|
||||
constexpr static KDCoordinate TableCellLabelTopMargin = 3;
|
||||
constexpr static KDCoordinate TableCellVerticalMargin = 3;
|
||||
constexpr static KDCoordinate TableCellHorizontalMargin = 10;
|
||||
constexpr static KDCoordinate TabHeight = 27;
|
||||
constexpr static KDCoordinate ScrollStep = 10;
|
||||
constexpr static KDCoordinate PopUpLeftMargin = 32;
|
||||
constexpr static KDCoordinate PopUpRightMargin = 32;
|
||||
constexpr static KDCoordinate PopUpLeftMargin = 27;
|
||||
constexpr static KDCoordinate PopUpRightMargin = 27;
|
||||
constexpr static KDCoordinate PopUpTopMargin = 50;
|
||||
constexpr static KDCoordinate ExamPopUpTopMargin = 27;
|
||||
constexpr static KDCoordinate ExamPopUpBottomMargin = 55;
|
||||
@@ -34,6 +35,7 @@ public:
|
||||
constexpr static KDCoordinate CellSeparatorThickness = 1;
|
||||
constexpr static KDCoordinate TableSeparatorThickness = 5;
|
||||
constexpr static KDCoordinate ExpressionViewHorizontalMargin = 5;
|
||||
constexpr static KDCoordinate EllipsisCellWidth = 37;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,7 @@ public:
|
||||
void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment,
|
||||
KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0);
|
||||
void reloadModalViewController();
|
||||
void dismissModalViewController();
|
||||
void dismissModalViewController(bool willExitApp = false);
|
||||
bool isDisplayingModal();
|
||||
void initView() override;
|
||||
void viewWillAppear() override;
|
||||
@@ -28,10 +28,10 @@ private:
|
||||
void setMainView(View * regularView);
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
void presentModalView(View * modalView, float verticalAlignment, float horizontalAlignment,
|
||||
KDCoordinate topMargin, KDCoordinate leftMargin, KDCoordinate bottomMargin, KDCoordinate rightMargin);
|
||||
void dismissModalView();
|
||||
void dismissModalView(bool willExitApp = false);
|
||||
bool isDisplayingModal() const;
|
||||
void reload();
|
||||
private:
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
#include <escher/scroll_view_indicator.h>
|
||||
|
||||
class ScrollView : public View {
|
||||
|
||||
/* TODO: Should we add a reload method that forces the relayouting of the
|
||||
* subviews? Or should ScrollView::setFrame always force the layouting of the
|
||||
* subviews ? Because the scroll view frame might not change but its content
|
||||
* might need to be relayouted.
|
||||
* cf TableView, InputViewController, EditExpressionController. */
|
||||
|
||||
public:
|
||||
ScrollView(View * contentView, ScrollViewDataSource * dataSource);
|
||||
ScrollView(ScrollView&& other);
|
||||
@@ -35,7 +42,7 @@ public:
|
||||
virtual ~Decorator() = default;
|
||||
virtual int numberOfIndicators() const { return 0; }
|
||||
virtual View * indicatorAtIndex(int index) { assert(false); return nullptr; }
|
||||
virtual KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) { return frame; }
|
||||
virtual KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) { return frame; }
|
||||
virtual void setBackgroundColor(KDColor c) {}
|
||||
};
|
||||
|
||||
@@ -44,7 +51,7 @@ public:
|
||||
BarDecorator() : m_verticalBar(), m_horizontalBar() {}
|
||||
int numberOfIndicators() const override { return 2; }
|
||||
View * indicatorAtIndex(int index) override;
|
||||
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) override;
|
||||
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) override;
|
||||
ScrollViewVerticalBar * verticalBar() { return &m_verticalBar; }
|
||||
ScrollViewHorizontalBar * horizontalBar() { return &m_horizontalBar; }
|
||||
private:
|
||||
@@ -63,7 +70,7 @@ public:
|
||||
{}
|
||||
int numberOfIndicators() const override { return 4; }
|
||||
View * indicatorAtIndex(int index) override;
|
||||
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) override;
|
||||
KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) override;
|
||||
void setBackgroundColor(KDColor c) override;
|
||||
private:
|
||||
ScrollViewArrow m_topArrow;
|
||||
@@ -96,7 +103,7 @@ protected:
|
||||
return m_frame.height() - m_topMargin - m_bottomMargin;
|
||||
}
|
||||
KDRect visibleContentRect();
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
virtual KDSize contentSize() const { return m_contentView->minimalSizeForOptimalDisplay(); }
|
||||
#if ESCHER_VIEW_LOGGING
|
||||
virtual const char * className() const override;
|
||||
|
||||
19
escher/include/escher/scrollable_expression_view.h
Normal file
19
escher/include/escher/scrollable_expression_view.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef ESCHER_SCROLLABLE_EXPRESSION_VIEW_H
|
||||
#define ESCHER_SCROLLABLE_EXPRESSION_VIEW_H
|
||||
|
||||
#include <escher/scrollable_view.h>
|
||||
#include <escher/scroll_view_data_source.h>
|
||||
#include <escher/expression_view.h>
|
||||
|
||||
class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource {
|
||||
public:
|
||||
ScrollableExpressionView(Responder * parentResponder, KDCoordinate leftRightMargin, KDCoordinate topBottomMargin, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite);
|
||||
Poincare::Layout layout() const;
|
||||
void setLayout(Poincare::Layout layout);
|
||||
void setBackgroundColor(KDColor backgroundColor) override;
|
||||
void setExpressionBackgroundColor(KDColor backgroundColor);
|
||||
private:
|
||||
ExpressionView m_expressionView;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,16 +1,18 @@
|
||||
#ifndef ESCHER_SELECTABLE_TABLE_VIEW_DELEGATE_H
|
||||
#define ESCHER_SELECTABLE_TABLE_VIEW_DELEGATE_H
|
||||
|
||||
#include <escher/context_provider.h>
|
||||
|
||||
class SelectableTableView;
|
||||
|
||||
class SelectableTableViewDelegate {
|
||||
class SelectableTableViewDelegate : public ContextProvider {
|
||||
public:
|
||||
/* withinTemporarySelection flag indicates when the selection change happens
|
||||
* in a temporary deselection: indeed, when reloading the data of the table,
|
||||
* we deselect the table before re-layouting the entire table and re-select
|
||||
* the previous selected cell. We might implement different course of action
|
||||
* when the selection change is 'real' or within temporary selection. */
|
||||
virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false);
|
||||
virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#ifndef ESCHER_STACK_VIEW_H
|
||||
#define ESCHER_STACK_VIEW_H
|
||||
|
||||
#include <escher/bordered.h>
|
||||
#include <escher/view.h>
|
||||
#include <escher/view_controller.h>
|
||||
|
||||
class StackView : public View {
|
||||
class StackView : public View, public Bordered {
|
||||
public:
|
||||
StackView();
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
private:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
|
||||
StackView m_stackViews[kMaxNumberOfStacks];
|
||||
View * m_contentView;
|
||||
|
||||
@@ -27,7 +27,7 @@ private:
|
||||
constexpr static KDCoordinate k_activeTabHeight = 5;
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
|
||||
constexpr static uint8_t k_maxNumberOfTabs = 4;
|
||||
TabViewCell m_cells[k_maxNumberOfTabs];
|
||||
|
||||
@@ -37,7 +37,7 @@ private:
|
||||
private:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
|
||||
View * m_activeView;
|
||||
};
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
#ifndef ESCHER_TABLE_CELL_H
|
||||
#define ESCHER_TABLE_CELL_H
|
||||
|
||||
#include <escher/bordered.h>
|
||||
#include <escher/highlight_cell.h>
|
||||
#include <escher/metric.h>
|
||||
|
||||
class TableCell : public HighlightCell {
|
||||
class TableCell : public Bordered, public HighlightCell {
|
||||
public:
|
||||
/* Layout enum class determines the way subviews are layouted.
|
||||
* We can split the cell vertically or horizontally.
|
||||
* We can choose which subviews frames are optimized (if there is not enough
|
||||
* space for all subviews, which one is cropped). This case happens so far only
|
||||
* for horizontally splitted cell, so we distinguish only these sub cases.
|
||||
* TODO: implement VerticalTopOverlap, VerticalBottomlap? */
|
||||
enum class Layout {
|
||||
Vertical,
|
||||
Horizontal
|
||||
HorizontalLeftOverlap, // Label overlaps on SubAccessory which overlaps on Accessory
|
||||
HorizontalRightOverlap, // Reverse
|
||||
};
|
||||
TableCell(Layout layout = Layout::Horizontal);
|
||||
TableCell(Layout layout = Layout::HorizontalLeftOverlap);
|
||||
virtual View * labelView() const;
|
||||
virtual View * accessoryView() const;
|
||||
virtual View * subAccessoryView() const;
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
constexpr static KDCoordinate k_labelMargin = 10;
|
||||
constexpr static KDCoordinate k_accessoryMargin = 10;
|
||||
protected:
|
||||
virtual KDCoordinate labelMargin() const { return Metric::TableCellHorizontalMargin; }
|
||||
virtual KDCoordinate accessoryMargin() const { return Metric::TableCellHorizontalMargin; }
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
constexpr static KDCoordinate k_verticalMargin = Metric::TableCellVerticalMargin;
|
||||
constexpr static KDCoordinate k_horizontalMargin = Metric::TableCellHorizontalMargin;
|
||||
private:
|
||||
constexpr static KDCoordinate k_accessoryBottomMargin = 3;
|
||||
Layout m_layout;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ protected:
|
||||
const char * className() const override;
|
||||
#endif
|
||||
TableViewDataSource * dataSource();
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
class ContentView : public View {
|
||||
public:
|
||||
ContentView(TableView * tableView, TableViewDataSource * dataSource, KDCoordinate horizontalCellOverlap, KDCoordinate verticalCellOverlap);
|
||||
@@ -42,7 +42,7 @@ protected:
|
||||
int numberOfDisplayableRows() const;
|
||||
int numberOfDisplayableColumns() const;
|
||||
KDRect cellFrame(int i, int j) const;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
protected:
|
||||
#if ESCHER_VIEW_LOGGING
|
||||
const char * className() const override;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
// See TODO in EditableField
|
||||
|
||||
class TextArea : public TextInput, public InputEventHandler {
|
||||
public:
|
||||
static constexpr int k_indentationSpaces = 2;
|
||||
@@ -80,6 +82,7 @@ protected:
|
||||
void insertSpacesAtLocation(int numberOfSpaces, char * location);
|
||||
|
||||
CodePoint removePreviousGlyph(char * * position);
|
||||
size_t removeText(const char * start, const char * end);
|
||||
size_t removeRemainingLine(const char * position, int direction);
|
||||
char operator[](size_t index) {
|
||||
assert(index < m_bufferSize);
|
||||
@@ -105,8 +108,8 @@ protected:
|
||||
m_cursorLocation = m_text.text();
|
||||
}
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
void drawStringAt(KDContext * ctx, int line, int column, const char * text, size_t length, KDColor textColor, KDColor backgroundColor) const;
|
||||
virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const = 0;
|
||||
void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const;
|
||||
virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0;
|
||||
virtual void clearRect(KDContext * ctx, KDRect rect) const = 0;
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
void setText(char * textBuffer, size_t textBufferSize);
|
||||
@@ -114,11 +117,12 @@ protected:
|
||||
const char * editedText() const override { return m_text.text(); }
|
||||
size_t editedTextLength() const override { return m_text.textLength(); }
|
||||
const Text * getText() const { return &m_text; }
|
||||
bool insertTextAtLocation(const char * text, const char * location) override;
|
||||
bool insertTextAtLocation(const char * text, char * location) override;
|
||||
void moveCursorGeo(int deltaX, int deltaY);
|
||||
bool removePreviousGlyph() override;
|
||||
bool removeEndOfLine() override;
|
||||
bool removeStartOfLine();
|
||||
size_t deleteSelection() override;
|
||||
protected:
|
||||
KDRect glyphFrameAtPosition(const char * text, const char * position) const override;
|
||||
Text m_text;
|
||||
@@ -126,6 +130,7 @@ protected:
|
||||
|
||||
ContentView * contentView() { return static_cast<ContentView *>(TextInput::contentView()); }
|
||||
private:
|
||||
void selectUpDown(bool up);
|
||||
TextAreaDelegate * m_delegate;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <escher/text_field_delegate.h>
|
||||
#include <string.h>
|
||||
|
||||
// See TODO in EditableField
|
||||
|
||||
/* TODO: TextField currently uses using 2 buffers:
|
||||
* - one to keep the displayed text
|
||||
* - another one to edit the text while keeping the previous text in the first
|
||||
@@ -28,7 +30,6 @@ public:
|
||||
char * draftTextBuffer() const { return const_cast<char *>(m_contentView.editedText()); }
|
||||
size_t draftTextLength() const;
|
||||
void setText(const char * text);
|
||||
void setAlignment(float horizontalAlignment, float verticalAlignment);
|
||||
void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); }
|
||||
CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override;
|
||||
bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override;
|
||||
@@ -37,12 +38,13 @@ public:
|
||||
return ContentView::k_maxBufferSize;
|
||||
}
|
||||
void scrollToCursor() override;
|
||||
bool shouldFinishEditing(Ion::Events::Event event) override { return m_delegate->textFieldShouldFinishEditing(this, event); }
|
||||
// TODO: factorize with TextField (see TODO of EditableField)
|
||||
bool shouldFinishEditing(Ion::Events::Event event) override;
|
||||
const KDFont * font() const { return m_contentView.font(); }
|
||||
protected:
|
||||
class ContentView : public TextInput::ContentView {
|
||||
public:
|
||||
ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor);
|
||||
ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor);
|
||||
void setBackgroundColor(KDColor backgroundColor);
|
||||
KDColor backgroundColor() const { return m_backgroundColor; }
|
||||
void setTextColor(KDColor textColor);
|
||||
@@ -52,19 +54,19 @@ protected:
|
||||
const char * editedText() const override;
|
||||
size_t editedTextLength() const override { return m_currentDraftTextLength; }
|
||||
void setText(const char * text);
|
||||
void setAlignment(float horizontalAlignment, float verticalAlignment);
|
||||
void setEditing(bool isEditing);
|
||||
void reinitDraftTextBuffer();
|
||||
void setDraftTextBufferSize(size_t size) { m_draftTextBufferSize = size; }
|
||||
/* If the text to be appended is too long to be added without overflowing the
|
||||
* buffer, nothing is done (not even adding few letters from the text to reach
|
||||
* the maximum buffer capacity) and false is returned. */
|
||||
bool insertTextAtLocation(const char * text, const char * location) override;
|
||||
bool insertTextAtLocation(const char * text, char * location) override;
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
bool removePreviousGlyph() override;
|
||||
bool removeEndOfLine() override;
|
||||
void willModifyTextBuffer();
|
||||
void didModifyTextBuffer();
|
||||
size_t deleteSelection() override;
|
||||
/* In some app (ie Calculation), text fields record expression results whose
|
||||
* lengths can reach 70 (ie
|
||||
* [[1.234567e-123*e^(1.234567e-123*i), 1.234567e-123*e^(1.234567e-123*i)]]).
|
||||
@@ -75,15 +77,13 @@ protected:
|
||||
* = 212 characters. */
|
||||
constexpr static int k_maxBufferSize = 220;
|
||||
private:
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
KDRect glyphFrameAtPosition(const char * buffer, const char * position) const override;
|
||||
bool m_isEditing;
|
||||
char * m_textBuffer;
|
||||
size_t m_textBufferSize;
|
||||
size_t m_draftTextBufferSize;
|
||||
size_t m_currentDraftTextLength;
|
||||
float m_horizontalAlignment;
|
||||
float m_verticalAlignment;
|
||||
KDColor m_textColor;
|
||||
KDColor m_backgroundColor;
|
||||
};
|
||||
@@ -92,7 +92,9 @@ protected:
|
||||
private:
|
||||
bool privateHandleEvent(Ion::Events::Event event);
|
||||
bool privateHandleMoveEvent(Ion::Events::Event event);
|
||||
bool privateHandleSelectEvent(Ion::Events::Event event);
|
||||
virtual void removeWholeText();
|
||||
bool storeInClipboard() const;
|
||||
TextFieldDelegate * m_delegate;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
// See TODO in EditableField
|
||||
|
||||
class TextInput : public ScrollableView, public ScrollViewDataSource {
|
||||
public:
|
||||
TextInput(Responder * parentResponder, View * contentView) : ScrollableView(parentResponder, contentView, this) {}
|
||||
@@ -15,32 +17,68 @@ public:
|
||||
const char * cursorLocation() const { return nonEditableContentView()->cursorLocation(); }
|
||||
bool setCursorLocation(const char * location);
|
||||
virtual void scrollToCursor();
|
||||
// Selection
|
||||
bool selectionIsEmpty() const { return nonEditableContentView()->selectionIsEmpty(); }
|
||||
void resetSelection() { contentView()->resetSelection(); }
|
||||
void deleteSelection();
|
||||
// Alignment
|
||||
void setAlignment(float horizontalAlignment, float verticalAlignment);
|
||||
protected:
|
||||
|
||||
class ContentView : public View {
|
||||
public:
|
||||
ContentView(const KDFont * font) :
|
||||
ContentView(const KDFont * font, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f) :
|
||||
View(),
|
||||
m_cursorView(),
|
||||
m_font(font),
|
||||
m_cursorLocation(nullptr)
|
||||
m_selectionStart(nullptr),
|
||||
m_selectionEnd(nullptr),
|
||||
m_cursorLocation(nullptr),
|
||||
m_horizontalAlignment(horizontalAlignment),
|
||||
m_verticalAlignment(verticalAlignment)
|
||||
{}
|
||||
|
||||
// Font
|
||||
void setFont(const KDFont * font);
|
||||
const KDFont * font() const { return m_font; }
|
||||
|
||||
// Cursor location
|
||||
const char * cursorLocation() const { assert(m_cursorLocation != nullptr); return m_cursorLocation; }
|
||||
void setCursorLocation(const char * cursorLocation);
|
||||
KDRect cursorRect();
|
||||
|
||||
// Virtual text get/add/remove
|
||||
virtual const char * text() const = 0;
|
||||
virtual bool insertTextAtLocation(const char * text, const char * location) = 0;
|
||||
virtual bool insertTextAtLocation(const char * text, char * location) = 0;
|
||||
virtual bool removePreviousGlyph() = 0;
|
||||
virtual bool removeEndOfLine() = 0;
|
||||
KDRect cursorRect();
|
||||
|
||||
// Selection
|
||||
const char * selectionStart() const { return m_selectionStart; }
|
||||
const char * selectionEnd() const { return m_selectionEnd; }
|
||||
void addSelection(const char * left, const char * right);
|
||||
bool resetSelection(); // returns true if the selection was indeed reset
|
||||
bool selectionIsEmpty() const;
|
||||
virtual size_t deleteSelection() = 0;
|
||||
|
||||
// Alignment
|
||||
void setAlignment(float horizontalAlignment, float verticalAlignment);
|
||||
float horizontalAlignment() const { return m_horizontalAlignment; }
|
||||
|
||||
// Reload
|
||||
void reloadRectFromPosition(const char * position, bool includeFollowingLines = false);
|
||||
protected:
|
||||
virtual void layoutSubviews() override;
|
||||
void reloadRectFromPosition(const char * position, bool lineBreak = false);
|
||||
virtual void layoutSubviews(bool force = false) override;
|
||||
void reloadRectFromAndToPositions(const char * start, const char * end);
|
||||
virtual KDRect glyphFrameAtPosition(const char * buffer, const char * position) const = 0;
|
||||
virtual KDRect dirtyRectFromPosition(const char * position, bool includeFollowingLines) const;
|
||||
TextCursorView m_cursorView;
|
||||
const KDFont * m_font;
|
||||
const char * m_selectionStart;
|
||||
const char * m_selectionEnd;
|
||||
const char * m_cursorLocation;
|
||||
virtual KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const;
|
||||
float m_horizontalAlignment;
|
||||
float m_verticalAlignment;
|
||||
private:
|
||||
int numberOfSubviews() const override { return 1; }
|
||||
View * subviewAtIndex(int index) override {
|
||||
@@ -54,7 +92,7 @@ protected:
|
||||
/* If the text to be appended is too long to be added without overflowing the
|
||||
* buffer, nothing is done (not even adding few letters from the text to reach
|
||||
* the maximum buffer capacity) and false is returned. */
|
||||
bool insertTextAtLocation(const char * textBuffer, const char * location);
|
||||
bool insertTextAtLocation(const char * textBuffer, char * location);
|
||||
bool removeEndOfLine();
|
||||
ContentView * contentView() {
|
||||
return const_cast<ContentView *>(nonEditableContentView());
|
||||
@@ -62,6 +100,7 @@ protected:
|
||||
virtual const ContentView * nonEditableContentView() const = 0;
|
||||
bool moveCursorLeft();
|
||||
bool moveCursorRight();
|
||||
bool selectLeftRight(bool left, bool all); // While indicates if all the text on the left/right should be selected
|
||||
private:
|
||||
virtual void willSetCursorLocation(const char * * location) {}
|
||||
virtual bool privateRemoveEndOfLine();
|
||||
|
||||
@@ -30,7 +30,7 @@ class View {
|
||||
friend class TransparentView;
|
||||
friend class Shared::RoundCursorView;
|
||||
public:
|
||||
View();
|
||||
View() : m_frame(KDRectZero), m_superview(nullptr), m_dirtyRect(KDRectZero) {}
|
||||
virtual ~View() {
|
||||
for (int i = 0; i < numberOfSubviews(); i++) {
|
||||
View * subview = subviewAtIndex(i);
|
||||
@@ -49,16 +49,18 @@ public:
|
||||
/* The drawRect method should be implemented by each View subclass. In a
|
||||
* typical drawRect implementation, a subclass will make drawing calls to the
|
||||
* Kandinsky library using the provided context. */
|
||||
virtual void drawRect(KDContext * ctx, KDRect rect) const;
|
||||
virtual void drawRect(KDContext * ctx, KDRect rect) const {
|
||||
// By default, a view doesn't do anything, it's transparent
|
||||
}
|
||||
|
||||
void setSize(KDSize size);
|
||||
void setFrame(KDRect frame);
|
||||
void setFrame(KDRect frame, bool force);
|
||||
KDPoint pointFromPointInView(View * view, KDPoint point);
|
||||
|
||||
KDRect bounds() const;
|
||||
View * subview(int index);
|
||||
|
||||
virtual KDSize minimalSizeForOptimalDisplay() const;
|
||||
virtual KDSize minimalSizeForOptimalDisplay() const { return KDSizeZero; }
|
||||
|
||||
#if ESCHER_VIEW_LOGGING
|
||||
friend std::ostream &operator<<(std::ostream &os, View &view);
|
||||
@@ -80,13 +82,9 @@ protected:
|
||||
#endif
|
||||
KDRect m_frame;
|
||||
private:
|
||||
virtual int numberOfSubviews() const {
|
||||
return 0;
|
||||
}
|
||||
virtual View * subviewAtIndex(int index) {
|
||||
return nullptr;
|
||||
}
|
||||
virtual void layoutSubviews();
|
||||
virtual int numberOfSubviews() const { return 0; }
|
||||
virtual View * subviewAtIndex(int index) { return nullptr; }
|
||||
virtual void layoutSubviews(bool force = false) {}
|
||||
virtual const Window * window() const;
|
||||
KDRect redraw(KDRect rect, KDRect forceRedrawRect = KDRectZero);
|
||||
KDPoint absoluteOrigin() const;
|
||||
|
||||
@@ -20,7 +20,7 @@ private:
|
||||
void setLabels(I18n::Message message1, I18n::Message message2);
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
private:
|
||||
constexpr static KDCoordinate k_topAndBottomMargin = 20;
|
||||
|
||||
@@ -13,7 +13,7 @@ protected:
|
||||
const char * className() const override;
|
||||
#endif
|
||||
virtual int numberOfSubviews() const override;
|
||||
virtual void layoutSubviews() override;
|
||||
virtual void layoutSubviews(bool force = false) override;
|
||||
virtual View * subviewAtIndex(int index) override;
|
||||
View * m_contentView;
|
||||
private:
|
||||
|
||||
@@ -22,11 +22,11 @@ View * AlternateEmptyViewController::ContentView::subviewAtIndex(int index) {
|
||||
return m_mainViewController->view();
|
||||
}
|
||||
|
||||
void AlternateEmptyViewController::ContentView::layoutSubviews() {
|
||||
void AlternateEmptyViewController::ContentView::layoutSubviews(bool force) {
|
||||
if (alternateEmptyViewDelegate()->isEmpty()) {
|
||||
m_delegate->emptyView()->setFrame(bounds());
|
||||
m_delegate->emptyView()->setFrame(bounds(), force);
|
||||
} else {
|
||||
m_mainViewController->view()->setFrame(bounds());
|
||||
m_mainViewController->view()->setFrame(bounds(), force);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,43 +5,16 @@ extern "C" {
|
||||
#include <assert.h>
|
||||
}
|
||||
|
||||
I18n::Message App::Descriptor::name() {
|
||||
return (I18n::Message)0;
|
||||
}
|
||||
|
||||
I18n::Message App::Descriptor::upperName() {
|
||||
return (I18n::Message)0;
|
||||
}
|
||||
|
||||
int App::Descriptor::examinationLevel() {
|
||||
return App::Descriptor::NoExaminationLevel;
|
||||
}
|
||||
|
||||
const Image * App::Descriptor::icon() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void App::Snapshot::pack(App * app) {
|
||||
tidy();
|
||||
app->~App();
|
||||
assert(Poincare::TreePool::sharedPool()->numberOfNodes() == 0);
|
||||
}
|
||||
|
||||
void App::Snapshot::reset() {
|
||||
}
|
||||
|
||||
void App::Snapshot::tidy() {
|
||||
}
|
||||
|
||||
App::App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage) :
|
||||
Responder(nullptr),
|
||||
m_modalViewController(this, rootViewController),
|
||||
m_firstResponder(nullptr),
|
||||
m_snapshot(snapshot),
|
||||
m_warningController(this, warningMessage)
|
||||
{
|
||||
}
|
||||
|
||||
bool App::processEvent(Ion::Events::Event event) {
|
||||
Responder * responder = m_firstResponder;
|
||||
bool didHandleEvent = false;
|
||||
@@ -55,10 +28,6 @@ bool App::processEvent(Ion::Events::Event event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Responder * App::firstResponder() {
|
||||
return m_firstResponder;
|
||||
}
|
||||
|
||||
void App::setFirstResponder(Responder * responder) {
|
||||
Responder * previousResponder = m_firstResponder;
|
||||
m_firstResponder = responder;
|
||||
@@ -90,8 +59,8 @@ void App::displayModalViewController(ViewController * vc, float verticalAlignmen
|
||||
m_modalViewController.displayModalViewController(vc, verticalAlignment, horizontalAlignment, topMargin, leftMargin, bottomMargin, rightMargin);
|
||||
}
|
||||
|
||||
void App::dismissModalViewController() {
|
||||
m_modalViewController.dismissModalViewController();
|
||||
void App::dismissModalViewController(bool willExitApp) {
|
||||
m_modalViewController.dismissModalViewController(willExitApp);
|
||||
}
|
||||
|
||||
void App::displayWarning(I18n::Message warningMessage1, I18n::Message warningMessage2, bool specialExitKeys) {
|
||||
@@ -109,7 +78,7 @@ void App::didBecomeActive(Window * window) {
|
||||
|
||||
void App::willBecomeInactive() {
|
||||
if (m_modalViewController.isDisplayingModal()) {
|
||||
dismissModalViewController();
|
||||
dismissModalViewController(true);
|
||||
}
|
||||
setFirstResponder(nullptr);
|
||||
m_modalViewController.viewDidDisappear();
|
||||
@@ -118,12 +87,3 @@ void App::willBecomeInactive() {
|
||||
View * App::modalView() {
|
||||
return m_modalViewController.view();
|
||||
}
|
||||
|
||||
int App::numberOfTimers() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Timer * App::timerAtIndex(int i) {
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
15
escher/src/bordered.cpp
Normal file
15
escher/src/bordered.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <escher/bordered.h>
|
||||
|
||||
void Bordered::drawBorderOfRect(KDContext * ctx, KDRect rect, KDColor borderColor) const {
|
||||
KDCoordinate width = rect.width();
|
||||
KDCoordinate height = rect.height();
|
||||
// Draw rectangle around cell
|
||||
ctx->fillRect(KDRect(0, 0, width, k_separatorThickness), borderColor);
|
||||
ctx->fillRect(KDRect(0, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), borderColor);
|
||||
ctx->fillRect(KDRect(width-k_separatorThickness, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), borderColor);
|
||||
ctx->fillRect(KDRect(0, height-k_separatorThickness, width, k_separatorThickness), borderColor);
|
||||
}
|
||||
|
||||
void Bordered::drawInnerRect(KDContext * ctx, KDRect rect, KDColor backgroundColor) const {
|
||||
ctx->fillRect(KDRect(k_separatorThickness, k_separatorThickness, rect.width()-2*k_separatorThickness, rect.height()-2*k_separatorThickness), backgroundColor);
|
||||
}
|
||||
@@ -24,8 +24,8 @@ View * Button::subviewAtIndex(int index) {
|
||||
return &m_messageTextView;
|
||||
}
|
||||
|
||||
void Button::layoutSubviews() {
|
||||
m_messageTextView.setFrame(bounds());
|
||||
void Button::layoutSubviews(bool force) {
|
||||
m_messageTextView.setFrame(bounds(), force);
|
||||
}
|
||||
|
||||
bool Button::handleEvent(Ion::Events::Event event) {
|
||||
|
||||
@@ -49,12 +49,12 @@ View * ButtonRowController::ContentView::subviewAtIndex(int index) {
|
||||
}
|
||||
}
|
||||
|
||||
void ButtonRowController::ContentView::layoutSubviews() {
|
||||
void ButtonRowController::ContentView::layoutSubviews(bool force) {
|
||||
/* Position the main view */
|
||||
if (numberOfButtons() == 0) {
|
||||
KDCoordinate margin = m_position == Position::Top ? 1 : 0;
|
||||
KDRect mainViewFrame(0, margin, bounds().width(), bounds().height()-margin);
|
||||
m_mainViewController->view()->setFrame(mainViewFrame);
|
||||
m_mainViewController->view()->setFrame(mainViewFrame, force);
|
||||
return;
|
||||
}
|
||||
KDCoordinate rowHeight;
|
||||
@@ -65,7 +65,7 @@ void ButtonRowController::ContentView::layoutSubviews() {
|
||||
}
|
||||
KDCoordinate frameOrigin = m_position == Position::Top ? rowHeight+1 : 0;
|
||||
KDRect mainViewFrame(0, frameOrigin, bounds().width(), bounds().height() - rowHeight - 1);
|
||||
m_mainViewController->view()->setFrame(mainViewFrame);
|
||||
m_mainViewController->view()->setFrame(mainViewFrame, force);
|
||||
|
||||
/* Position buttons */
|
||||
int nbOfButtons = numberOfButtons();
|
||||
@@ -88,7 +88,7 @@ void ButtonRowController::ContentView::layoutSubviews() {
|
||||
Button * button = buttonAtIndex(i);
|
||||
KDCoordinate buttonWidth = button->minimalSizeForOptimalDisplay().width();
|
||||
KDRect buttonFrame(currentXOrigin, yOrigin, buttonWidth, buttonHeight);
|
||||
button->setFrame(buttonFrame);
|
||||
button->setFrame(buttonFrame, force);
|
||||
currentXOrigin += buttonWidth + widthMargin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
static Clipboard s_clipboard;
|
||||
|
||||
static inline int minInt(int x, int y) { return x < y ? x : y; }
|
||||
|
||||
Clipboard * Clipboard::sharedClipboard() {
|
||||
return &s_clipboard;
|
||||
}
|
||||
|
||||
void Clipboard::store(const char * storedText) {
|
||||
strlcpy(m_textBuffer, storedText, TextField::maxBufferSize());
|
||||
void Clipboard::store(const char * storedText, int length) {
|
||||
strlcpy(m_textBuffer, storedText, length == -1 ? TextField::maxBufferSize() : minInt(TextField::maxBufferSize(), length + 1));
|
||||
}
|
||||
|
||||
void Clipboard::reset() {
|
||||
|
||||
@@ -43,12 +43,13 @@ View * EditableTextCell::subviewAtIndex(int index) {
|
||||
return &m_textField;
|
||||
}
|
||||
|
||||
void EditableTextCell::layoutSubviews() {
|
||||
void EditableTextCell::layoutSubviews(bool force) {
|
||||
KDRect cellBounds = bounds();
|
||||
m_textField.setFrame(KDRect(cellBounds.x() + m_leftMargin,
|
||||
cellBounds.y() + m_topMargin,
|
||||
cellBounds.width() - m_leftMargin - m_rightMargin,
|
||||
cellBounds.height() - m_topMargin - m_bottomMargin));
|
||||
cellBounds.height() - m_topMargin - m_bottomMargin),
|
||||
force);
|
||||
}
|
||||
|
||||
void EditableTextCell::didBecomeFirstResponder() {
|
||||
|
||||
@@ -38,8 +38,8 @@ View * EvenOddBufferTextCell::subviewAtIndex(int index) {
|
||||
return &m_bufferTextView;
|
||||
}
|
||||
|
||||
void EvenOddBufferTextCell::layoutSubviews() {
|
||||
void EvenOddBufferTextCell::layoutSubviews(bool force) {
|
||||
KDRect boundsThis = bounds();
|
||||
KDRect boundsBuffer = KDRect(boundsThis.left() + k_horizontalMargin, boundsThis.top(), boundsThis.width() - 2*k_horizontalMargin, boundsThis.height());
|
||||
m_bufferTextView.setFrame(boundsBuffer);
|
||||
m_bufferTextView.setFrame(boundsBuffer, force);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ EvenOddCellWithEllipsis::EvenOddCellWithEllipsis() :
|
||||
{
|
||||
}
|
||||
|
||||
void EvenOddCellWithEllipsis::layoutSubviews() {
|
||||
m_ellipsisView.setFrame(bounds());
|
||||
void EvenOddCellWithEllipsis::layoutSubviews(bool force) {
|
||||
m_ellipsisView.setFrame(bounds(), force);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ View * EvenOddEditableTextCell::subviewAtIndex(int index) {
|
||||
return &m_editableCell;
|
||||
}
|
||||
|
||||
void EvenOddEditableTextCell::layoutSubviews() {
|
||||
m_editableCell.setFrame(bounds());
|
||||
void EvenOddEditableTextCell::layoutSubviews(bool force) {
|
||||
m_editableCell.setFrame(bounds(), force);
|
||||
}
|
||||
|
||||
void EvenOddEditableTextCell::didBecomeFirstResponder() {
|
||||
|
||||
@@ -65,6 +65,6 @@ View * EvenOddExpressionCell::subviewAtIndex(int index) {
|
||||
return &m_expressionView;
|
||||
}
|
||||
|
||||
void EvenOddExpressionCell::layoutSubviews() {
|
||||
m_expressionView.setFrame(KDRect(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height()));
|
||||
void EvenOddExpressionCell::layoutSubviews(bool force) {
|
||||
m_expressionView.setFrame(KDRect(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height()), force);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ View * EvenOddMessageTextCell::subviewAtIndex(int index) {
|
||||
return &m_messageTextView;
|
||||
}
|
||||
|
||||
void EvenOddMessageTextCell::layoutSubviews() {
|
||||
void EvenOddMessageTextCell::layoutSubviews(bool force) {
|
||||
KDRect boundsThis = bounds();
|
||||
m_messageTextView.setFrame(KDRect(k_horizontalMargin, 0, boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()));
|
||||
m_messageTextView.setFrame(KDRect(k_horizontalMargin, 0, boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()), force);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur
|
||||
ExpressionField::ExpressionField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) :
|
||||
Responder(parentResponder),
|
||||
View(),
|
||||
m_inputViewMemoizedHeight(0),
|
||||
m_textField(parentResponder, nullptr, k_textFieldBufferSize, k_textFieldBufferSize, inputEventHandlerDelegate, textFieldDelegate, KDFont::LargeFont, 0.0f, 0.5f, Palette::PrimaryText, Palette::ExpressionInputBackground),
|
||||
m_layoutField(parentResponder, inputEventHandlerDelegate, layoutFieldDelegate)
|
||||
{
|
||||
@@ -39,7 +40,7 @@ bool ExpressionField::isEditing() const {
|
||||
|
||||
const char * ExpressionField::text() {
|
||||
if (!editionIsInTextField()) {
|
||||
m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize);
|
||||
m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize, m_layoutField.context());
|
||||
}
|
||||
return m_textField.draftTextBuffer();
|
||||
}
|
||||
@@ -62,20 +63,15 @@ View * ExpressionField::subviewAtIndex(int index) {
|
||||
return &m_layoutField;
|
||||
}
|
||||
|
||||
void ExpressionField::layoutSubviews() {
|
||||
void ExpressionField::layoutSubviews(bool force) {
|
||||
KDRect inputViewFrame(0, k_separatorThickness, bounds().width(), bounds().height() - k_separatorThickness);
|
||||
if (editionIsInTextField()) {
|
||||
m_textField.setFrame(inputViewFrame);
|
||||
m_layoutField.setFrame(KDRectZero);
|
||||
m_textField.setFrame(inputViewFrame, force);
|
||||
m_layoutField.setFrame(KDRectZero, force);
|
||||
return;
|
||||
}
|
||||
m_layoutField.setFrame(inputViewFrame);
|
||||
m_textField.setFrame(KDRectZero);
|
||||
}
|
||||
|
||||
void ExpressionField::reload() {
|
||||
layoutSubviews();
|
||||
markRectAsDirty(bounds());
|
||||
m_layoutField.setFrame(inputViewFrame, force);
|
||||
m_textField.setFrame(KDRectZero, force);
|
||||
}
|
||||
|
||||
void ExpressionField::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
@@ -87,6 +83,10 @@ bool ExpressionField::handleEvent(Ion::Events::Event event) {
|
||||
return editionIsInTextField() ? m_textField.handleEvent(event) : m_layoutField.handleEvent(event);
|
||||
}
|
||||
|
||||
void ExpressionField::didBecomeFirstResponder() {
|
||||
m_inputViewMemoizedHeight = inputViewHeight();
|
||||
}
|
||||
|
||||
KDSize ExpressionField::minimalSizeForOptimalDisplay() const {
|
||||
return KDSize(0, inputViewHeight());
|
||||
}
|
||||
@@ -99,8 +99,11 @@ bool ExpressionField::isEmpty() const {
|
||||
return editionIsInTextField() ? (m_textField.draftTextLength() == 0) : !m_layoutField.hasText();
|
||||
}
|
||||
|
||||
bool ExpressionField::heightIsMaximal() const {
|
||||
return inputViewHeight() == k_separatorThickness + maximalHeight();
|
||||
bool ExpressionField::inputViewHeightDidChange() {
|
||||
KDCoordinate newHeight = inputViewHeight();
|
||||
bool didChange = m_inputViewMemoizedHeight != newHeight;
|
||||
m_inputViewMemoizedHeight = newHeight;
|
||||
return didChange;
|
||||
}
|
||||
|
||||
bool ExpressionField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) {
|
||||
@@ -113,11 +116,7 @@ bool ExpressionField::handleEventWithText(const char * text, bool indentation, b
|
||||
|
||||
KDCoordinate ExpressionField::inputViewHeight() const {
|
||||
return k_separatorThickness
|
||||
+ (editionIsInTextField() ? k_textFieldHeight :
|
||||
minCoordinate(maximalHeight(),
|
||||
maxCoordinate(k_textFieldHeight, m_layoutField.minimalSizeForOptimalDisplay().height())));
|
||||
}
|
||||
|
||||
KDCoordinate ExpressionField::maximalHeight() const {
|
||||
return 0.6*Ion::Display::Height;
|
||||
+ (editionIsInTextField() ? k_minimalHeight :
|
||||
minCoordinate(k_maximalHeight,
|
||||
maxCoordinate(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height())));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <escher/expression_table_cell.h>
|
||||
#include <escher/container.h>
|
||||
#include <escher/palette.h>
|
||||
#include <assert.h>
|
||||
|
||||
ExpressionTableCell::ExpressionTableCell(Layout layout) :
|
||||
ExpressionTableCell::ExpressionTableCell(Responder * parentResponder, Layout layout) :
|
||||
Responder(parentResponder),
|
||||
TableCell(layout),
|
||||
m_labelExpressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::ListCellBackground)
|
||||
m_labelExpressionView(this, k_horizontalMargin, 0, 0.0f, 0.5f, Palette::PrimaryText, Palette::ListCellBackground)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -24,3 +26,7 @@ void ExpressionTableCell::setLayout(Poincare::Layout layout) {
|
||||
layoutSubviews();
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionTableCell::didBecomeFirstResponder() {
|
||||
Container::activeApp()->setFirstResponder(&m_labelExpressionView);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <escher/expression_table_cell_with_expression.h>
|
||||
#include <escher/container.h>
|
||||
#include <escher/palette.h>
|
||||
#include <assert.h>
|
||||
|
||||
ExpressionTableCellWithExpression::ExpressionTableCellWithExpression() :
|
||||
ExpressionTableCell(Layout::Horizontal),
|
||||
m_accessoryExpressionView(1.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground)
|
||||
ExpressionTableCellWithExpression::ExpressionTableCellWithExpression(Responder * parentResponder) :
|
||||
ExpressionTableCell(parentResponder, Layout::HorizontalLeftOverlap),
|
||||
m_accessoryExpressionView(this, k_horizontalMargin, 0, 1.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground)
|
||||
{}
|
||||
|
||||
View * ExpressionTableCellWithExpression::accessoryView() const {
|
||||
@@ -21,3 +22,7 @@ void ExpressionTableCellWithExpression::setAccessoryLayout(Poincare::Layout l) {
|
||||
m_accessoryExpressionView.setLayout(l);
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
void ExpressionTableCellWithExpression::didBecomeFirstResponder() {
|
||||
Container::activeApp()->setFirstResponder(&m_accessoryExpressionView);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
#include <escher/palette.h>
|
||||
#include <assert.h>
|
||||
|
||||
ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(I18n::Message accessoryMessage, Layout layout) :
|
||||
ExpressionTableCell(layout),
|
||||
ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(Responder * parentResponder, I18n::Message accessoryMessage, Layout layout) :
|
||||
ExpressionTableCell(parentResponder, layout),
|
||||
m_accessoryView(KDFont::SmallFont, accessoryMessage, 0.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground)
|
||||
{
|
||||
if (layout == Layout::Horizontal) {
|
||||
if (layout != Layout::Vertical) {
|
||||
m_accessoryView.setAlignment(1.0f, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#include <escher/expression_view.h>
|
||||
#include <escher/palette.h>
|
||||
|
||||
using namespace Poincare;
|
||||
|
||||
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
|
||||
|
||||
ExpressionView::ExpressionView(float horizontalAlignment, float verticalAlignment,
|
||||
KDColor textColor, KDColor backgroundColor) :
|
||||
KDColor textColor, KDColor backgroundColor, Poincare::Layout * selectionStart, Poincare::Layout * selectionEnd ) :
|
||||
m_layout(),
|
||||
m_horizontalAlignment(horizontalAlignment),
|
||||
m_verticalAlignment(verticalAlignment),
|
||||
m_textColor(textColor),
|
||||
m_backgroundColor(backgroundColor),
|
||||
m_selectionStart(selectionStart),
|
||||
m_selectionEnd(selectionEnd),
|
||||
m_horizontalAlignment(horizontalAlignment),
|
||||
m_verticalAlignment(verticalAlignment),
|
||||
m_horizontalMargin(0)
|
||||
{
|
||||
}
|
||||
@@ -71,6 +75,6 @@ KDPoint ExpressionView::absoluteDrawingOrigin() const {
|
||||
void ExpressionView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
ctx->fillRect(rect, m_backgroundColor);
|
||||
if (!m_layout.isUninitialized()) {
|
||||
m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor);
|
||||
m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor, m_selectionStart, m_selectionEnd, Palette::Select);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ InputViewController::InputViewController(Responder * parentResponder, ViewContro
|
||||
m_failureAction(Invocation(nullptr, nullptr)),
|
||||
m_inputEventHandlerDelegate(inputEventHandlerDelegate),
|
||||
m_textFieldDelegate(textFieldDelegate),
|
||||
m_layoutFieldDelegate(layoutFieldDelegate),
|
||||
m_inputViewHeightIsMaximal(false)
|
||||
m_layoutFieldDelegate(layoutFieldDelegate)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -86,12 +85,17 @@ bool InputViewController::layoutFieldDidAbortEditing(LayoutField * layoutField)
|
||||
}
|
||||
|
||||
void InputViewController::layoutFieldDidChangeSize(LayoutField * layoutField) {
|
||||
/* Reload the view only if the ExpressionField height actually changes, i.e.
|
||||
* not if the height is already maximal and stays maximal. */
|
||||
bool newInputViewHeightIsMaximal = m_expressionFieldController.expressionField()->heightIsMaximal();
|
||||
if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) {
|
||||
m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal;
|
||||
if (m_expressionFieldController.expressionField()->inputViewHeightDidChange()) {
|
||||
/* Reload the whole view only if the ExpressionField's height did actually
|
||||
* change. */
|
||||
reloadModalViewController();
|
||||
} else {
|
||||
/* The input view is already at maximal size so we do not need to relayout
|
||||
* the view underneath, but the view inside the input view might still need
|
||||
* to be relayouted.
|
||||
* We force the relayout because the frame stays the same but we need to
|
||||
* propagate a relayout to the content of the field scroll view. */
|
||||
m_expressionFieldController.expressionField()->layoutSubviews(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ static inline KDCoordinate minCoordinate(KDCoordinate x, KDCoordinate y) { retur
|
||||
|
||||
LayoutField::ContentView::ContentView() :
|
||||
m_cursor(),
|
||||
m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard),
|
||||
m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard, &m_selectionStart, &m_selectionEnd),
|
||||
m_cursorView(),
|
||||
m_selectionStart(),
|
||||
m_selectionEnd(),
|
||||
m_isEditing(false)
|
||||
{
|
||||
clearLayout();
|
||||
@@ -47,28 +49,202 @@ KDSize LayoutField::ContentView::minimalSizeForOptimalDisplay() const {
|
||||
return KDSize(evSize.width() + Poincare::LayoutCursor::k_cursorWidth, evSize.height());
|
||||
}
|
||||
|
||||
bool IsBefore(Layout& l1, Layout& l2, bool strict) {
|
||||
char * node1 = reinterpret_cast<char *>(l1.node());
|
||||
char * node2 = reinterpret_cast<char *>(l2.node());
|
||||
return strict ? (node1 < node2) : (node1 <= node2);
|
||||
}
|
||||
|
||||
void LayoutField::ContentView::addSelection(Layout addedLayout) {
|
||||
KDRect rectBefore = selectionRect();
|
||||
if (selectionIsEmpty()) {
|
||||
/*
|
||||
* ---------- -> +++ is the previous previous selection
|
||||
* ( ) -> added selection
|
||||
* ---+++++-- -> next selection
|
||||
* */
|
||||
m_selectionStart = addedLayout;
|
||||
m_selectionEnd = addedLayout;
|
||||
} else if (IsBefore(m_selectionEnd, addedLayout, true)) {
|
||||
/*
|
||||
* +++------- -> +++ is the previous previous selection
|
||||
* ( ) -> added selection
|
||||
* ++++++++++ -> next selection
|
||||
* */
|
||||
if (addedLayout.parent() == m_selectionStart) {
|
||||
/* The previous selected layout is an horizontal layout and we remove one
|
||||
* of its children. */
|
||||
assert(m_selectionStart == m_selectionEnd
|
||||
&& m_selectionStart.type() == LayoutNode::Type::HorizontalLayout);
|
||||
m_selectionStart = m_selectionStart.childAtIndex(0);
|
||||
m_selectionEnd = m_selectionEnd.childAtIndex(m_selectionEnd.numberOfChildren() - 1);
|
||||
addSelection(addedLayout);
|
||||
return;
|
||||
}
|
||||
/* The previous selected layouts and the new added selection are all
|
||||
* children of a same horizontal layout. */
|
||||
assert(m_selectionStart.parent() == m_selectionEnd.parent()
|
||||
&& m_selectionStart.parent() == addedLayout.parent()
|
||||
&& m_selectionStart.parent().type() == LayoutNode::Type::HorizontalLayout);
|
||||
m_selectionEnd = addedLayout;
|
||||
} else if (IsBefore(addedLayout, m_selectionStart, true)) {
|
||||
/*
|
||||
* -------+++ -> +++ is the previous previous selection
|
||||
* ( ) -> added selection
|
||||
* ++++++++++ -> next selection
|
||||
* */
|
||||
if (addedLayout.type() == LayoutNode::Type::HorizontalLayout
|
||||
&& m_selectionStart.parent() == addedLayout)
|
||||
{
|
||||
/* The selection was from the first to the last child of an horizontal
|
||||
* layout, we add this horizontal layout -> the selection is now empty. */
|
||||
assert(m_selectionEnd.parent() == addedLayout);
|
||||
assert(addedLayout.childAtIndex(0) == m_selectionStart);
|
||||
assert(addedLayout.childAtIndex(addedLayout.numberOfChildren() - 1) == m_selectionEnd);
|
||||
m_selectionStart = Layout();
|
||||
m_selectionEnd = Layout();
|
||||
} else {
|
||||
if (m_selectionStart.hasAncestor(addedLayout, true)) {
|
||||
// We are selecting a layout containing the current selection
|
||||
m_selectionEnd = addedLayout;
|
||||
}
|
||||
m_selectionStart = addedLayout;
|
||||
}
|
||||
} else {
|
||||
bool sameEnd = m_selectionEnd == addedLayout;
|
||||
bool sameStart = m_selectionStart == addedLayout;
|
||||
if (sameStart && sameEnd) {
|
||||
/*
|
||||
* -----+++++ -> +++ is the previous previous selection
|
||||
* ( ) -> added selection
|
||||
* ---------- -> next selection
|
||||
* */
|
||||
m_selectionStart = Layout();
|
||||
m_selectionEnd = Layout();
|
||||
} else {
|
||||
assert(sameStart || sameEnd);
|
||||
/*
|
||||
* ++++++++++ -> +++ is the previous previous selection
|
||||
* ( ) -> added selection if sameStart
|
||||
* ( ) -> added selection if sameEnd
|
||||
* +++++----- -> next selection
|
||||
* The previous selected layouts and the new "added" selection are all
|
||||
* children of a same horizontal layout. */
|
||||
Layout horizontalParent = m_selectionStart.parent();
|
||||
assert(!horizontalParent.isUninitialized()
|
||||
&& horizontalParent == m_selectionEnd.parent()
|
||||
&& horizontalParent == addedLayout.parent()
|
||||
&& horizontalParent.type() == LayoutNode::Type::HorizontalLayout
|
||||
&& ((sameEnd && horizontalParent.indexOfChild(m_selectionEnd) > 0)
|
||||
|| (sameStart && horizontalParent.indexOfChild(m_selectionStart) < horizontalParent.numberOfChildren())));
|
||||
if (sameStart) {
|
||||
m_selectionStart = horizontalParent.childAtIndex(horizontalParent.indexOfChild(m_selectionStart) + 1);
|
||||
} else {
|
||||
m_selectionEnd = horizontalParent.childAtIndex(horizontalParent.indexOfChild(m_selectionEnd) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KDRect rectAfter = selectionRect();
|
||||
// We need to update the background color for selected/unselected layouts
|
||||
markRectAsDirty(rectBefore.unionedWith(rectAfter));
|
||||
}
|
||||
|
||||
bool LayoutField::ContentView::resetSelection() {
|
||||
if (selectionIsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
m_selectionStart = Layout();
|
||||
m_selectionEnd = Layout();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LayoutField::ContentView::copySelection(Context * context) {
|
||||
if (selectionIsEmpty()) {
|
||||
return;
|
||||
}
|
||||
constexpr int bufferSize = TextField::maxBufferSize();
|
||||
char buffer[bufferSize];
|
||||
|
||||
if (m_selectionStart == m_selectionEnd) {
|
||||
m_selectionStart.serializeParsedExpression(buffer, bufferSize, context);
|
||||
if (buffer[0] == 0) {
|
||||
int offset = 0;
|
||||
if (m_selectionStart.type() == LayoutNode::Type::VerticalOffsetLayout) {
|
||||
assert(bufferSize > 1);
|
||||
buffer[offset++] = UCodePointEmpty;
|
||||
}
|
||||
m_selectionStart.serializeForParsing(buffer + offset, bufferSize - offset);
|
||||
}
|
||||
} else {
|
||||
Layout selectionParent = m_selectionStart.parent();
|
||||
assert(!selectionParent.isUninitialized());
|
||||
assert(selectionParent.type() == LayoutNode::Type::HorizontalLayout);
|
||||
int firstIndex = selectionParent.indexOfChild(m_selectionStart);
|
||||
int lastIndex = selectionParent.indexOfChild(m_selectionEnd);
|
||||
static_cast<HorizontalLayout&>(selectionParent).serializeChildren(firstIndex, lastIndex, buffer, bufferSize);
|
||||
}
|
||||
if (buffer[0] != 0) {
|
||||
Clipboard::sharedClipboard()->store(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
bool LayoutField::ContentView::selectionIsEmpty() const {
|
||||
assert(!m_selectionStart.isUninitialized() || m_selectionEnd.isUninitialized());
|
||||
assert(!m_selectionEnd.isUninitialized() || m_selectionStart.isUninitialized());
|
||||
return m_selectionStart.isUninitialized();
|
||||
}
|
||||
|
||||
void LayoutField::ContentView::deleteSelection() {
|
||||
assert(!selectionIsEmpty());
|
||||
Layout selectionParent = m_selectionStart.parent();
|
||||
|
||||
/* If the selected layout is the upmost layout, it must be an horizontal
|
||||
* layout. Empty it. */
|
||||
if (selectionParent.isUninitialized()) {
|
||||
assert(m_selectionStart == m_selectionEnd);
|
||||
assert(m_selectionStart.type() == LayoutNode::Type::HorizontalLayout);
|
||||
clearLayout();
|
||||
} else {
|
||||
assert(selectionParent == m_selectionEnd.parent());
|
||||
// Remove the selected children or replace it with an empty layout.
|
||||
if (selectionParent.type() == LayoutNode::Type::HorizontalLayout) {
|
||||
int firstIndex = selectionParent.indexOfChild(m_selectionStart);
|
||||
int lastIndex = m_selectionStart == m_selectionEnd ? firstIndex : selectionParent.indexOfChild(m_selectionEnd);
|
||||
for (int i = lastIndex; i >= firstIndex; i--) {
|
||||
static_cast<HorizontalLayout&>(selectionParent).removeChildAtIndex(i, &m_cursor, false);
|
||||
}
|
||||
} else {
|
||||
// Only one child can be selected
|
||||
assert(m_selectionStart == m_selectionEnd);
|
||||
selectionParent.replaceChildWithEmpty(m_selectionStart, &m_cursor);
|
||||
}
|
||||
}
|
||||
resetSelection();
|
||||
}
|
||||
|
||||
View * LayoutField::ContentView::subviewAtIndex(int index) {
|
||||
assert(index >= 0 && index < 2);
|
||||
assert(0 <= index && index < numberOfSubviews());
|
||||
View * m_views[] = {&m_expressionView, &m_cursorView};
|
||||
return m_views[index];
|
||||
}
|
||||
|
||||
void LayoutField::ContentView::layoutSubviews() {
|
||||
m_expressionView.setFrame(bounds());
|
||||
layoutCursorSubview();
|
||||
void LayoutField::ContentView::layoutSubviews(bool force) {
|
||||
m_expressionView.setFrame(bounds(), force);
|
||||
layoutCursorSubview(force);
|
||||
}
|
||||
|
||||
void LayoutField::ContentView::layoutCursorSubview() {
|
||||
void LayoutField::ContentView::layoutCursorSubview(bool force) {
|
||||
if (!m_isEditing) {
|
||||
m_cursorView.setFrame(KDRectZero);
|
||||
m_cursorView.setFrame(KDRectZero, force);
|
||||
return;
|
||||
}
|
||||
KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin();
|
||||
Layout pointedLayoutR = m_cursor.layoutReference();
|
||||
Layout pointedLayoutR = m_cursor.layout();
|
||||
LayoutCursor::Position cursorPosition = m_cursor.position();
|
||||
LayoutCursor eqCursor = pointedLayoutR.equivalentCursor(&m_cursor);
|
||||
if (eqCursor.isDefined() && pointedLayoutR.hasChild(eqCursor.layoutReference())) {
|
||||
pointedLayoutR = eqCursor.layoutReference();
|
||||
if (eqCursor.isDefined() && pointedLayoutR.hasChild(eqCursor.layout())) {
|
||||
pointedLayoutR = eqCursor.layout();
|
||||
cursorPosition = eqCursor.position();
|
||||
}
|
||||
KDPoint cursoredExpressionViewOrigin = pointedLayoutR.absoluteOrigin();
|
||||
@@ -76,8 +252,28 @@ void LayoutField::ContentView::layoutCursorSubview() {
|
||||
if (cursorPosition == LayoutCursor::Position::Right) {
|
||||
cursorX += pointedLayoutR.layoutSize().width();
|
||||
}
|
||||
KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baseline());
|
||||
m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeight()));
|
||||
if (selectionIsEmpty()) {
|
||||
KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baselineWithoutSelection());
|
||||
m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeightWithoutSelection()), force);
|
||||
} else {
|
||||
KDRect cursorRect = selectionRect();
|
||||
KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursorRect.y());
|
||||
m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, cursorRect.height()), force);
|
||||
}
|
||||
}
|
||||
|
||||
KDRect LayoutField::ContentView::selectionRect() const {
|
||||
if (selectionIsEmpty()) {
|
||||
return KDRectZero;
|
||||
}
|
||||
if (m_selectionStart == m_selectionEnd) {
|
||||
return KDRect(m_selectionStart.absoluteOrigin(), m_selectionStart.layoutSize());
|
||||
}
|
||||
Layout selectionParent = m_selectionStart.parent();
|
||||
assert(m_selectionEnd.parent() == selectionParent);
|
||||
assert(selectionParent.type() == LayoutNode::Type::HorizontalLayout);
|
||||
KDRect selectionRectInParent = static_cast<HorizontalLayout &>(selectionParent).relativeSelectionRect(&m_selectionStart, &m_selectionEnd);
|
||||
return selectionRectInParent.translatedBy(selectionParent.absoluteOrigin());
|
||||
}
|
||||
|
||||
void LayoutField::setEditing(bool isEditing) {
|
||||
@@ -87,14 +283,23 @@ void LayoutField::setEditing(bool isEditing) {
|
||||
}
|
||||
}
|
||||
|
||||
Context * LayoutField::context() const {
|
||||
return (m_delegate != nullptr) ? m_delegate->context() : nullptr;
|
||||
}
|
||||
|
||||
CodePoint LayoutField::XNTCodePoint(CodePoint defaultXNTCodePoint) {
|
||||
CodePoint xnt = m_contentView.cursor()->layoutReference().XNTCodePoint();
|
||||
CodePoint xnt = m_contentView.cursor()->layout().XNTCodePoint();
|
||||
if (xnt != UCodePointNull) {
|
||||
return xnt;
|
||||
}
|
||||
return defaultXNTCodePoint;
|
||||
}
|
||||
|
||||
void LayoutField::putCursorRightOfLayout() {
|
||||
m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixAncestors();
|
||||
m_contentView.setCursor(LayoutCursor(m_contentView.expressionView()->layout(), LayoutCursor::Position::Right));
|
||||
}
|
||||
|
||||
void LayoutField::reload(KDSize previousSize) {
|
||||
layout().invalidAllSizesPositionsAndBaselines();
|
||||
KDSize newSize = minimalSizeForOptimalDisplay();
|
||||
@@ -111,6 +316,12 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool
|
||||
* - the result of a key pressed, such as "," or "cos(•)"
|
||||
* - the text added after a toolbox selection
|
||||
* - the result of a copy-paste. */
|
||||
|
||||
// Delete the selected layouts if needed
|
||||
if (!m_contentView.selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
}
|
||||
|
||||
if (text[0] == 0) {
|
||||
// The text is empty
|
||||
return true;
|
||||
@@ -142,11 +353,11 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool
|
||||
} else if((strcmp(text, Ion::Events::Multiplication.text())) == 0){
|
||||
m_contentView.cursor()->addMultiplicationPointLayout();
|
||||
} else {
|
||||
Expression resultExpression = Expression::Parse(text);
|
||||
Expression resultExpression = Expression::Parse(text, nullptr);
|
||||
if (resultExpression.isUninitialized()) {
|
||||
// The text is not parsable (for instance, ",") and is added char by char.
|
||||
KDSize previousLayoutSize = minimalSizeForOptimalDisplay();
|
||||
m_contentView.cursor()->insertText(text);
|
||||
m_contentView.cursor()->insertText(text, forceCursorRightOfText);
|
||||
reload(previousLayoutSize);
|
||||
return true;
|
||||
}
|
||||
@@ -160,6 +371,14 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayoutField::shouldFinishEditing(Ion::Events::Event event) {
|
||||
if (m_delegate->layoutFieldShouldFinishEditing(this, event)) {
|
||||
resetSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayoutField::handleEvent(Ion::Events::Event event) {
|
||||
bool didHandleEvent = false;
|
||||
KDSize previousSize = minimalSizeForOptimalDisplay();
|
||||
@@ -171,6 +390,23 @@ bool LayoutField::handleEvent(Ion::Events::Event event) {
|
||||
}
|
||||
shouldRecomputeLayout = shouldRecomputeLayout || moveEventChangedLayout;
|
||||
didHandleEvent = true;
|
||||
} else if (privateHandleSelectionEvent(event, &shouldRecomputeLayout)) {
|
||||
didHandleEvent = true;
|
||||
// Handle matrices
|
||||
if (!m_contentView.selectionIsEmpty()) {
|
||||
bool removedSquares = false;
|
||||
Layout * selectStart = m_contentView.selectionStart();
|
||||
Layout * selectEnd = m_contentView.selectionEnd();
|
||||
if (*selectStart != *selectEnd) {
|
||||
Layout p = selectStart->parent();
|
||||
assert(p == selectEnd->parent());
|
||||
assert(p.type() == LayoutNode::Type::HorizontalLayout);
|
||||
removedSquares = p.removeGreySquaresFromAllMatrixChildren();
|
||||
} else {
|
||||
removedSquares = selectStart->removeGreySquaresFromAllMatrixChildren();
|
||||
}
|
||||
shouldRecomputeLayout = m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout;
|
||||
}
|
||||
} else if (privateHandleEvent(event)) {
|
||||
shouldRecomputeLayout = true;
|
||||
didHandleEvent = true;
|
||||
@@ -191,6 +427,10 @@ bool LayoutField::handleEvent(Ion::Events::Event event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void LayoutField::deleteSelection() {
|
||||
m_contentView.deleteSelection();
|
||||
}
|
||||
|
||||
bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
|
||||
if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(this, event)) {
|
||||
return true;
|
||||
@@ -201,11 +441,12 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (isEditing() && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method?
|
||||
if (isEditing() && m_delegate && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method?
|
||||
setEditing(false);
|
||||
if (m_delegate->layoutFieldDidFinishEditing(this, layout(), event)) {
|
||||
// Reinit layout for next use
|
||||
clearLayout();
|
||||
resetSelection();
|
||||
} else {
|
||||
setEditing(true);
|
||||
}
|
||||
@@ -225,6 +466,7 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
|
||||
}
|
||||
if (event == Ion::Events::Back && isEditing()) {
|
||||
clearLayout();
|
||||
resetSelection();
|
||||
setEditing(false);
|
||||
m_delegate->layoutFieldDidAbortEditing(this);
|
||||
return true;
|
||||
@@ -243,40 +485,51 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) {
|
||||
handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
|
||||
} else {
|
||||
assert(event == Ion::Events::Backspace);
|
||||
m_contentView.cursor()->performBackspace();
|
||||
if (!m_contentView.selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
} else {
|
||||
m_contentView.cursor()->performBackspace();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Copy && isEditing()) {
|
||||
m_contentView.copySelection(context());
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Clear && isEditing()) {
|
||||
clearLayout();
|
||||
resetSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool IsSimpleMoveEvent(Ion::Events::Event event) {
|
||||
return event == Ion::Events::Left
|
||||
|| event == Ion::Events::Right
|
||||
|| event == Ion::Events::Up
|
||||
|| event == Ion::Events::Down;
|
||||
}
|
||||
|
||||
bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) {
|
||||
if (!IsSimpleMoveEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
if (resetSelection()) {
|
||||
*shouldRecomputeLayout = true;
|
||||
return true;
|
||||
}
|
||||
LayoutCursor result;
|
||||
if (event == Ion::Events::Left) {
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Left, shouldRecomputeLayout);
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Left, shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::Right) {
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Right, shouldRecomputeLayout);
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Right, shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::Up) {
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Up, shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::Down) {
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Down, shouldRecomputeLayout);
|
||||
} else if (event == Ion::Events::ShiftLeft) {
|
||||
*shouldRecomputeLayout = true;
|
||||
if (m_contentView.cursor()->layoutReference().removeGreySquaresFromAllMatrixAncestors()) {
|
||||
*shouldRecomputeLayout = true;
|
||||
}
|
||||
result.setLayout(layout());
|
||||
result.setPosition(LayoutCursor::Position::Left);
|
||||
} else if (event == Ion::Events::ShiftRight) {
|
||||
if (m_contentView.cursor()->layoutReference().removeGreySquaresFromAllMatrixAncestors()) {
|
||||
*shouldRecomputeLayout = true;
|
||||
}
|
||||
result.setLayout(layout());
|
||||
result.setPosition(LayoutCursor::Position::Right);
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Up, shouldRecomputeLayout);
|
||||
} else {
|
||||
assert(event == Ion::Events::Down);
|
||||
result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Down, shouldRecomputeLayout);
|
||||
}
|
||||
if (result.isDefined()) {
|
||||
m_contentView.setCursor(result);
|
||||
@@ -285,6 +538,29 @@ bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * should
|
||||
return false;
|
||||
}
|
||||
|
||||
bool eventIsSelection(Ion::Events::Event event) {
|
||||
return event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight || event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown;
|
||||
}
|
||||
|
||||
bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) {
|
||||
if (!eventIsSelection(event)) {
|
||||
return false;
|
||||
}
|
||||
Layout addedSelection;
|
||||
LayoutCursor::Direction direction = event == Ion::Events::ShiftLeft ? LayoutCursor::Direction::Left :
|
||||
(event == Ion::Events::ShiftRight ? LayoutCursor::Direction::Right :
|
||||
(event == Ion::Events::ShiftUp ? LayoutCursor::Direction::Up :
|
||||
LayoutCursor::Direction::Down));
|
||||
LayoutCursor result = m_contentView.cursor()->selectAtDirection(direction, shouldRecomputeLayout, &addedSelection);
|
||||
if (addedSelection.isUninitialized()) {
|
||||
return false;
|
||||
}
|
||||
m_contentView.addSelection(addedSelection);
|
||||
assert(result.isDefined());
|
||||
m_contentView.setCursor(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
void LayoutField::scrollRightOfLayout(Layout layoutR) {
|
||||
KDRect layoutRect(layoutR.absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layoutR.layoutSize());
|
||||
scrollToBaselinedRect(layoutRect, layoutR.baseline());
|
||||
@@ -374,7 +650,7 @@ void LayoutField::insertLayoutAtCursor(Layout layoutR, Poincare::Expression corr
|
||||
}
|
||||
|
||||
// Handle matrices
|
||||
cursor->layoutReference().addGreySquaresToAllMatrixAncestors();
|
||||
cursor->layout().addGreySquaresToAllMatrixAncestors();
|
||||
|
||||
// Handle empty layouts
|
||||
cursor->hideEmptyLayoutIfNeeded();
|
||||
|
||||
@@ -46,12 +46,17 @@ void MessageTableCellWithEditableText::setTextColor(KDColor color) {
|
||||
MessageTableCell::setTextColor(color);
|
||||
}
|
||||
|
||||
void MessageTableCellWithEditableText::layoutSubviews() {
|
||||
TableCell::layoutSubviews();
|
||||
void MessageTableCellWithEditableText::layoutSubviews(bool force) {
|
||||
TableCell::layoutSubviews(force);
|
||||
KDSize textFieldSize = m_textField.minimalSizeForOptimalDisplay();
|
||||
KDSize labelSize = labelView()->minimalSizeForOptimalDisplay();
|
||||
/* Handle textfield that has no defined width (as their width evolves with
|
||||
* the length of edited text */
|
||||
textFieldSize = KDSize(bounds().width() - 2*k_separatorThickness - labelSize.width()-2*k_labelMargin-k_accessoryMargin, textFieldSize.height());
|
||||
m_textField.setFrame(KDRect(bounds().width() - textFieldSize.width() - k_separatorThickness-k_accessoryMargin, (bounds().height()-textFieldSize.height()-k_accessoryMargin)/2, textFieldSize.width(), textFieldSize.height()+k_accessoryMargin));
|
||||
textFieldSize = KDSize(bounds().width() - 2*k_separatorThickness - labelSize.width()-2*labelMargin()-k_horizontalMargin, textFieldSize.height());
|
||||
m_textField.setFrame(KDRect(
|
||||
bounds().width() - textFieldSize.width() - k_separatorThickness-k_horizontalMargin,
|
||||
(bounds().height()-textFieldSize.height()-k_horizontalMargin)/2,
|
||||
textFieldSize.width(),
|
||||
textFieldSize.height()+k_horizontalMargin),
|
||||
force);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <escher/message_tree.h>
|
||||
|
||||
int MessageTree::numberOfChildren() const {
|
||||
return m_numberOfChildren;
|
||||
}
|
||||
|
||||
I18n::Message MessageTree::label() const {
|
||||
return m_label;
|
||||
}
|
||||
|
||||
bool MessageTree::isNull() const {
|
||||
return (m_label == (I18n::Message)0);
|
||||
}
|
||||
@@ -54,15 +54,15 @@ KDRect ModalViewController::ContentView::modalViewFrame() const {
|
||||
return modalViewFrame;
|
||||
}
|
||||
|
||||
void ModalViewController::ContentView::layoutSubviews() {
|
||||
void ModalViewController::ContentView::layoutSubviews(bool force) {
|
||||
assert(m_regularView != nullptr);
|
||||
m_regularView->setFrame(bounds());
|
||||
m_regularView->setFrame(bounds(), force);
|
||||
if (m_isDisplayingModal) {
|
||||
assert(m_currentModalView != nullptr);
|
||||
m_currentModalView->setFrame(modalViewFrame());
|
||||
m_currentModalView->setFrame(modalViewFrame(), force);
|
||||
} else {
|
||||
if (m_currentModalView) {
|
||||
m_currentModalView->setFrame(KDRectZero);
|
||||
m_currentModalView->setFrame(KDRectZero, force);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,9 +80,11 @@ void ModalViewController::ContentView::presentModalView(View * modalView, float
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
void ModalViewController::ContentView::dismissModalView() {
|
||||
void ModalViewController::ContentView::dismissModalView(bool willExitApp) {
|
||||
m_isDisplayingModal = false;
|
||||
layoutSubviews();
|
||||
if (!willExitApp) {
|
||||
layoutSubviews();
|
||||
}
|
||||
m_currentModalView->resetSuperview();
|
||||
m_currentModalView = nullptr;
|
||||
}
|
||||
@@ -128,10 +130,12 @@ void ModalViewController::reloadModalViewController() {
|
||||
m_contentView.layoutSubviews();
|
||||
}
|
||||
|
||||
void ModalViewController::dismissModalViewController() {
|
||||
void ModalViewController::dismissModalViewController(bool willExitApp) {
|
||||
m_currentModalViewController->viewDidDisappear();
|
||||
Container::activeApp()->setFirstResponder(m_previousResponder);
|
||||
m_contentView.dismissModalView();
|
||||
if (!willExitApp) {
|
||||
Container::activeApp()->setFirstResponder(m_previousResponder);
|
||||
}
|
||||
m_contentView.dismissModalView(willExitApp);
|
||||
m_currentModalViewController = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ View * NestedMenuController::ListController::view() {
|
||||
|
||||
void NestedMenuController::ListController::didBecomeFirstResponder() {
|
||||
m_selectableTableView->reloadData();
|
||||
m_selectableTableView->selectCellAtLocation(0, m_firstSelectedRow);
|
||||
Container::activeApp()->setFirstResponder(m_selectableTableView);
|
||||
m_selectableTableView->selectCellAtLocation(0, m_firstSelectedRow);
|
||||
}
|
||||
|
||||
void NestedMenuController::ListController::setFirstSelectedRow(int firstSelectedRow) {
|
||||
|
||||
@@ -93,20 +93,20 @@ KDRect ScrollView::visibleContentRect() {
|
||||
m_frame.height() - m_topMargin - m_bottomMargin);
|
||||
}
|
||||
|
||||
void ScrollView::layoutSubviews() {
|
||||
void ScrollView::layoutSubviews(bool force) {
|
||||
KDRect r1 = KDRectZero;
|
||||
KDRect r2 = KDRectZero;
|
||||
KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2);
|
||||
KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2, force);
|
||||
if (!r1.isEmpty()) {
|
||||
markRectAsDirty(r1);
|
||||
}
|
||||
if (!r2.isEmpty()) {
|
||||
markRectAsDirty(r2);
|
||||
}
|
||||
m_innerView.setFrame(innerFrame);
|
||||
m_innerView.setFrame(innerFrame, force);
|
||||
KDPoint absoluteOffset = contentOffset().opposite().translatedBy(KDPoint(m_leftMargin - innerFrame.x(), m_topMargin - innerFrame.y()));
|
||||
KDRect contentFrame = KDRect(absoluteOffset, contentSize());
|
||||
m_contentView->setFrame(contentFrame);
|
||||
m_contentView->setFrame(contentFrame, force);
|
||||
}
|
||||
|
||||
void ScrollView::setContentOffset(KDPoint offset, bool forceRelayout) {
|
||||
@@ -136,7 +136,7 @@ View * ScrollView::BarDecorator::indicatorAtIndex(int index) {
|
||||
return &m_horizontalBar;
|
||||
}
|
||||
|
||||
KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) {
|
||||
KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) {
|
||||
bool hBarWasVisible = m_horizontalBar.visible();
|
||||
bool hBarIsVisible = m_horizontalBar.update(content.width(), offset.x(), frame.width());
|
||||
bool vBarWasVisible = m_verticalBar.visible();
|
||||
@@ -152,13 +152,13 @@ KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset
|
||||
/* If the two indicators are visible, we leave an empty rectangle in the right
|
||||
* bottom corner. Otherwise, the only indicator uses all the height/width. */
|
||||
m_verticalBar.setFrame(KDRect(
|
||||
frame.width() - vBarFrameBreadth, 0,
|
||||
vBarFrameBreadth, frame.height() - hBarFrameBreadth
|
||||
));
|
||||
frame.width() - vBarFrameBreadth, 0,
|
||||
vBarFrameBreadth, frame.height() - hBarFrameBreadth),
|
||||
force);
|
||||
m_horizontalBar.setFrame(KDRect(
|
||||
0, frame.height() - hBarFrameBreadth,
|
||||
frame.width() - vBarFrameBreadth, hBarFrameBreadth
|
||||
));
|
||||
0, frame.height() - hBarFrameBreadth,
|
||||
frame.width() - vBarFrameBreadth, hBarFrameBreadth),
|
||||
force);
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ View * ScrollView::ArrowDecorator::indicatorAtIndex(int index) {
|
||||
}
|
||||
}
|
||||
|
||||
KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) {
|
||||
KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) {
|
||||
// There is no need to dirty the rects
|
||||
KDSize arrowSize = KDFont::LargeFont->glyphSize();
|
||||
KDCoordinate topArrowFrameBreadth = arrowSize.height() * m_topArrow.update(0 < offset.y());
|
||||
@@ -184,21 +184,21 @@ KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offs
|
||||
KDCoordinate bottomArrowFrameBreadth = arrowSize.height() * m_bottomArrow.update(offset.y() + frame.height() < content.height());
|
||||
KDCoordinate leftArrowFrameBreadth = arrowSize.width() * m_leftArrow.update(0 < offset.x());
|
||||
m_topArrow.setFrame(KDRect(
|
||||
0, 0,
|
||||
frame.width(), topArrowFrameBreadth
|
||||
));
|
||||
0, 0,
|
||||
frame.width(), topArrowFrameBreadth),
|
||||
force);
|
||||
m_rightArrow.setFrame(KDRect(
|
||||
frame.width() - rightArrowFrameBreadth, 0,
|
||||
rightArrowFrameBreadth, frame.height()
|
||||
));
|
||||
frame.width() - rightArrowFrameBreadth, 0,
|
||||
rightArrowFrameBreadth, frame.height()),
|
||||
force);
|
||||
m_bottomArrow.setFrame(KDRect(
|
||||
0, frame.height() - bottomArrowFrameBreadth,
|
||||
frame.width(), bottomArrowFrameBreadth
|
||||
));
|
||||
0, frame.height() - bottomArrowFrameBreadth,
|
||||
frame.width(), bottomArrowFrameBreadth),
|
||||
force);
|
||||
m_leftArrow.setFrame(KDRect(
|
||||
0, 0,
|
||||
leftArrowFrameBreadth, frame.height()
|
||||
));
|
||||
0, 0,
|
||||
leftArrowFrameBreadth, frame.height()),
|
||||
force);
|
||||
return KDRect(
|
||||
frame.x() + leftArrowFrameBreadth,
|
||||
frame.y() + topArrowFrameBreadth,
|
||||
|
||||
35
escher/src/scrollable_expression_view.cpp
Normal file
35
escher/src/scrollable_expression_view.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <escher/scrollable_expression_view.h>
|
||||
#include <poincare/layout.h>
|
||||
#include <escher/metric.h>
|
||||
#include <assert.h>
|
||||
|
||||
ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder, KDCoordinate leftRightMargin, KDCoordinate topBottomMargin, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
|
||||
ScrollableView(parentResponder, &m_expressionView, this),
|
||||
m_expressionView(horizontalAlignment, verticalAlignment, textColor, backgroundColor)
|
||||
{
|
||||
setDecoratorType(ScrollView::Decorator::Type::Arrows);
|
||||
setMargins(
|
||||
topBottomMargin,
|
||||
leftRightMargin,
|
||||
topBottomMargin,
|
||||
leftRightMargin
|
||||
);
|
||||
setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
Poincare::Layout ScrollableExpressionView::layout() const {
|
||||
return m_expressionView.layout();
|
||||
}
|
||||
|
||||
void ScrollableExpressionView::setLayout(Poincare::Layout layout) {
|
||||
m_expressionView.setLayout(layout);
|
||||
}
|
||||
|
||||
void ScrollableExpressionView::setBackgroundColor(KDColor backgroundColor) {
|
||||
m_expressionView.setBackgroundColor(backgroundColor);
|
||||
ScrollableView::setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
void ScrollableExpressionView::setExpressionBackgroundColor(KDColor backgroundColor) {
|
||||
m_expressionView.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
@@ -58,7 +58,9 @@ void SelectableTableView::didEnterResponderChain(Responder * previousFirstRespon
|
||||
}
|
||||
|
||||
void SelectableTableView::willExitResponderChain(Responder * nextFirstResponder) {
|
||||
unhighlightSelectedCell();
|
||||
if (nextFirstResponder != nullptr) {
|
||||
unhighlightSelectedCell();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectableTableView::deselectTable(bool withinTemporarySelection) {
|
||||
@@ -149,7 +151,7 @@ bool SelectableTableView::handleEvent(Ion::Events::Event event) {
|
||||
if (!l.isUninitialized()) {
|
||||
constexpr int bufferSize = TextField::maxBufferSize();
|
||||
char buffer[bufferSize];
|
||||
l.serializeParsedExpression(buffer, bufferSize);
|
||||
l.serializeParsedExpression(buffer, bufferSize, m_delegate == nullptr ? nullptr : m_delegate->context());
|
||||
Clipboard::sharedClipboard()->store(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#include <escher/selectable_table_view_delegate.h>
|
||||
|
||||
void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#include <escher/solid_text_area.h>
|
||||
|
||||
static inline int minInt(int x, int y) { return x < y ? x : y; }
|
||||
|
||||
void SolidTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const {
|
||||
ctx->fillRect(rect, m_backgroundColor);
|
||||
}
|
||||
|
||||
void SolidTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const {
|
||||
drawStringAt(
|
||||
ctx,
|
||||
line,
|
||||
fromColumn,
|
||||
text + fromColumn,
|
||||
minInt(length - fromColumn, toColumn - fromColumn),
|
||||
m_textColor,
|
||||
m_backgroundColor
|
||||
);
|
||||
}
|
||||
@@ -30,13 +30,11 @@ void StackView::setNamedController(ViewController * controller) {
|
||||
}
|
||||
|
||||
void StackView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
const KDFont * font = KDFont::SmallFont;
|
||||
KDCoordinate height = bounds().height();
|
||||
KDCoordinate width = bounds().width();
|
||||
ctx->fillRect(KDRect(0, 0, width, 1), m_separatorColor);
|
||||
ctx->fillRect(KDRect(0, 1, width, height-2), m_backgroundColor);
|
||||
ctx->fillRect(KDRect(0, height-1, width, 1), m_separatorColor);
|
||||
KDRect b = bounds();
|
||||
drawBorderOfRect(ctx, b, m_separatorColor);
|
||||
drawInnerRect(ctx, b, m_backgroundColor);
|
||||
// Write title
|
||||
const KDFont * font = KDFont::SmallFont;
|
||||
KDSize textSize = font->stringSize(m_controller->title());
|
||||
KDPoint origin((m_frame.width() - textSize.width())/2,(m_frame.height() - textSize.height())/2);
|
||||
ctx->drawString(m_controller->title(), origin, font, m_textColor, m_backgroundColor);
|
||||
|
||||
@@ -36,20 +36,20 @@ void StackViewController::ControllerView::popStack() {
|
||||
m_numberOfStacks--;
|
||||
}
|
||||
|
||||
void StackViewController::ControllerView::layoutSubviews() {
|
||||
void StackViewController::ControllerView::layoutSubviews(bool force) {
|
||||
KDCoordinate width = m_frame.width();
|
||||
if (m_displayStackHeaders) {
|
||||
for (int i=0; i<m_numberOfStacks; i++) {
|
||||
m_stackViews[i].setFrame(KDRect(0, Metric::StackTitleHeight*i, width, Metric::StackTitleHeight + 1));
|
||||
m_stackViews[i].setFrame(KDRect(0, Metric::StackTitleHeight*i, width, Metric::StackTitleHeight + 1), force);
|
||||
}
|
||||
}
|
||||
if (m_contentView) {
|
||||
KDCoordinate separatorHeight = m_numberOfStacks > 0 ? 1 : 0;
|
||||
KDRect contentViewFrame = KDRect( 0,
|
||||
KDRect contentViewFrame = KDRect(0,
|
||||
m_displayStackHeaders * (m_numberOfStacks * Metric::StackTitleHeight + separatorHeight),
|
||||
width,
|
||||
m_frame.height() - m_displayStackHeaders * m_numberOfStacks * Metric::StackTitleHeight);
|
||||
m_contentView->setFrame(contentViewFrame);
|
||||
m_contentView->setFrame(contentViewFrame, force);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ View * TabView::subviewAtIndex(int index) {
|
||||
return &m_cells[index];
|
||||
}
|
||||
|
||||
void TabView::layoutSubviews() {
|
||||
void TabView::layoutSubviews(bool force) {
|
||||
KDCoordinate emptyWidth = bounds().width();
|
||||
for (int i=0; i<m_numberOfTabs; i++) {
|
||||
emptyWidth -= m_cells[i].minimalSizeForOptimalDisplay().width();
|
||||
@@ -82,7 +82,7 @@ void TabView::layoutSubviews() {
|
||||
widthUsed, 0,
|
||||
tabWidth, m_frame.height() - k_activeTabHeight
|
||||
);
|
||||
m_cells[i].setFrame(cellFrame);
|
||||
m_cells[i].setFrame(cellFrame, force);
|
||||
widthUsed += tabWidth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,19 @@ void TabViewController::ContentView::setActiveView(View * view) {
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TabViewController::ContentView::layoutSubviews() {
|
||||
void TabViewController::ContentView::layoutSubviews(bool force) {
|
||||
KDRect tabViewFrame = KDRect(
|
||||
0, 0,
|
||||
m_frame.width(), Metric::TabHeight
|
||||
);
|
||||
m_tabView.setFrame(tabViewFrame);
|
||||
m_tabView.setFrame(tabViewFrame, force);
|
||||
if (m_activeView) {
|
||||
KDRect activeViewFrame = KDRect(
|
||||
0, Metric::TabHeight,
|
||||
m_frame.width(),
|
||||
m_frame.height() - Metric::TabHeight
|
||||
);
|
||||
m_activeView->setFrame(activeViewFrame);
|
||||
m_activeView->setFrame(activeViewFrame, force);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
#include <escher/palette.h>
|
||||
#include <escher/metric.h>
|
||||
|
||||
static inline KDCoordinate minCoordinate(KDCoordinate x, KDCoordinate y) { return x < y ? x : y; }
|
||||
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
|
||||
|
||||
TableCell::TableCell(Layout layout) :
|
||||
Bordered(),
|
||||
HighlightCell(),
|
||||
m_layout(layout)
|
||||
{
|
||||
@@ -38,77 +42,131 @@ View * TableCell::subviewAtIndex(int index) {
|
||||
* margins (like ExpressionView), sometimes the subview has no margins (like
|
||||
* MessageView) which prevents us to handle margins only here. */
|
||||
|
||||
void TableCell::layoutSubviews() {
|
||||
KDCoordinate withMargin(KDCoordinate length, KDCoordinate margin) {
|
||||
return length == 0 ? 0 : length + margin;
|
||||
}
|
||||
|
||||
void TableCell::layoutSubviews(bool force) {
|
||||
/* TODO: this code is awful. However, this should handle multiples cases
|
||||
* (subviews are not defined, margins are overriden...) */
|
||||
KDCoordinate width = bounds().width();
|
||||
KDCoordinate height = bounds().height();
|
||||
View * label = labelView();
|
||||
KDSize labelSize = label ? label->minimalSizeForOptimalDisplay() : KDSizeZero;
|
||||
if (label) {
|
||||
switch (m_layout) {
|
||||
case Layout::Vertical:
|
||||
label->setFrame(KDRect(
|
||||
k_separatorThickness+k_labelMargin,
|
||||
k_separatorThickness+Metric::TableCellLabelTopMargin,
|
||||
width-2*k_separatorThickness-k_labelMargin,
|
||||
labelSize.height()));
|
||||
break;
|
||||
default:
|
||||
label->setFrame(KDRect(
|
||||
k_separatorThickness+k_labelMargin,
|
||||
k_separatorThickness,
|
||||
labelSize.width(),
|
||||
height - 2*k_separatorThickness));
|
||||
break;
|
||||
}
|
||||
}
|
||||
View * accessory = accessoryView();
|
||||
if (accessory) {
|
||||
KDSize accessorySize = accessory->minimalSizeForOptimalDisplay();
|
||||
switch (m_layout) {
|
||||
case Layout::Vertical:
|
||||
accessory->setFrame(KDRect(
|
||||
k_separatorThickness+k_accessoryMargin,
|
||||
height-k_separatorThickness-accessorySize.height()-k_accessoryBottomMargin,
|
||||
width-2*k_separatorThickness - k_accessoryMargin,
|
||||
accessorySize.height()));
|
||||
break;
|
||||
default:
|
||||
// In some cases, the accessory view cannot take all the size it can
|
||||
KDCoordinate wantedX = width-accessorySize.width()-k_separatorThickness-k_accessoryMargin;
|
||||
KDCoordinate minX = label ? label->bounds().x()+labelSize.width()+k_labelMargin+k_separatorThickness+k_accessoryMargin : k_accessoryMargin;
|
||||
if (minX < wantedX) {
|
||||
accessory->setFrame(KDRect(
|
||||
wantedX,
|
||||
k_separatorThickness,
|
||||
accessorySize.width(),
|
||||
height-2*k_separatorThickness));
|
||||
} else {
|
||||
accessory->setFrame(KDRect(
|
||||
minX,
|
||||
k_separatorThickness,
|
||||
accessorySize.width(),
|
||||
height-2*k_separatorThickness));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
View * subAccessory = subAccessoryView();
|
||||
if (subAccessory && accessory) {
|
||||
KDSize accessorySize = accessory->minimalSizeForOptimalDisplay();
|
||||
KDSize subAccessorySize = subAccessory->minimalSizeForOptimalDisplay();
|
||||
subAccessory->setFrame(KDRect(width-k_separatorThickness-k_accessoryMargin-accessorySize.width()-subAccessorySize.width(), k_separatorThickness,
|
||||
subAccessorySize.width(), height-2*k_separatorThickness));
|
||||
KDSize labelSize = label ? label->minimalSizeForOptimalDisplay() : KDSizeZero;
|
||||
KDSize accessorySize = accessory ? accessory->minimalSizeForOptimalDisplay() : KDSizeZero;
|
||||
KDSize subAccessorySize = subAccessory ? subAccessory->minimalSizeForOptimalDisplay() : KDSizeZero;
|
||||
if (m_layout == Layout::Vertical) {
|
||||
/*
|
||||
* Vertically:
|
||||
* ----------------
|
||||
* ----------------
|
||||
* Line separator
|
||||
* ----------------
|
||||
* k_verticalMargin
|
||||
* ----------------
|
||||
* LABEL
|
||||
* ----------------
|
||||
* k_verticalMargin
|
||||
* ----------------
|
||||
* .
|
||||
* . [White space if possible, otherwise LABEL overlaps SUBACCESSORY and so on]
|
||||
* .
|
||||
* ----------------
|
||||
* SUBACCESSORY
|
||||
* ----------------
|
||||
* ACCESSORY
|
||||
* ----------------
|
||||
* k_verticalMargin
|
||||
* ----------------
|
||||
* Line separator
|
||||
* ----------------
|
||||
* ----------------
|
||||
*
|
||||
*
|
||||
* Horizontally:
|
||||
* || Line separator | margin* | SUBVIEW | margin* | Line separator ||
|
||||
*
|
||||
* * = margin can either be labelMargin(), accessoryMargin() or k_horizontalMargin depending on the subview
|
||||
*
|
||||
* */
|
||||
KDCoordinate horizontalMargin = k_separatorThickness + labelMargin();
|
||||
KDCoordinate y = k_separatorThickness;
|
||||
if (label) {
|
||||
y += k_verticalMargin;
|
||||
KDCoordinate labelHeight = minCoordinate(labelSize.height(), height - y - k_separatorThickness - k_verticalMargin);
|
||||
label->setFrame(KDRect(horizontalMargin, y, width-2*horizontalMargin, labelHeight), force);
|
||||
y += labelHeight + k_verticalMargin;
|
||||
}
|
||||
horizontalMargin = k_separatorThickness + k_horizontalMargin;
|
||||
y = maxCoordinate(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin) - withMargin(subAccessorySize.height(), 0));
|
||||
if (subAccessory) {
|
||||
KDCoordinate subAccessoryHeight = minCoordinate(subAccessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin);
|
||||
accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, subAccessoryHeight), force);
|
||||
y += subAccessoryHeight;
|
||||
}
|
||||
horizontalMargin = k_separatorThickness + accessoryMargin();
|
||||
y = maxCoordinate(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin));
|
||||
if (accessory) {
|
||||
KDCoordinate accessoryHeight = minCoordinate(accessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin);
|
||||
accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, accessoryHeight), force);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Vertically:
|
||||
* ----------------
|
||||
* ----------------
|
||||
* Line separator
|
||||
* ----------------
|
||||
* SUBVIEW
|
||||
* ----------------
|
||||
* Line separator
|
||||
* ----------------
|
||||
* ----------------
|
||||
*
|
||||
* Horizontally:
|
||||
* || Line separator | Label margin | LABEL | Label margin | ...
|
||||
* [ White space if possible otherwise the overlap can be from left to
|
||||
* right subviews or the contrary ]
|
||||
*
|
||||
* ... | SUBACCESSORY | ACCESSORY | Accessory margin | Line separator ||
|
||||
*
|
||||
* */
|
||||
|
||||
KDCoordinate verticalMargin = k_separatorThickness;
|
||||
KDCoordinate x = 0;
|
||||
KDCoordinate labelX = k_separatorThickness + labelMargin();
|
||||
KDCoordinate subAccessoryX = maxCoordinate(k_separatorThickness + k_horizontalMargin, width - k_separatorThickness - withMargin(accessorySize.width(), accessoryMargin()) - withMargin(subAccessorySize.width(), 0));
|
||||
KDCoordinate accessoryX = maxCoordinate(k_separatorThickness + accessoryMargin(), width - k_separatorThickness - withMargin(accessorySize.width(), accessoryMargin()));
|
||||
if (label) {
|
||||
x = labelX;
|
||||
KDCoordinate labelWidth = minCoordinate(labelSize.width(), width - x - k_separatorThickness - labelMargin());
|
||||
if (m_layout == Layout::HorizontalRightOverlap) {
|
||||
labelWidth = minCoordinate(labelWidth, subAccessoryX - x - labelMargin());
|
||||
}
|
||||
label->setFrame(KDRect(x, verticalMargin, labelWidth, height-2*verticalMargin), force);
|
||||
x += labelWidth + labelMargin();
|
||||
}
|
||||
if (subAccessory) {
|
||||
x = maxCoordinate(x, subAccessoryX);
|
||||
KDCoordinate subAccessoryWidth = minCoordinate(subAccessorySize.width(), width - x - k_separatorThickness - k_horizontalMargin);
|
||||
if (m_layout == Layout::HorizontalRightOverlap) {
|
||||
subAccessoryWidth = minCoordinate(subAccessoryWidth, accessoryX - x);
|
||||
}
|
||||
subAccessory->setFrame(KDRect(x, verticalMargin, subAccessoryWidth, height-2*verticalMargin), force);
|
||||
x += subAccessoryWidth;
|
||||
}
|
||||
if (accessory) {
|
||||
x = maxCoordinate(x, accessoryX);
|
||||
KDCoordinate accessoryWidth = minCoordinate(accessorySize.width(), width - x - k_separatorThickness - accessoryMargin());
|
||||
accessory->setFrame(KDRect(x, verticalMargin, accessoryWidth, height-2*verticalMargin), force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableCell::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
KDCoordinate width = bounds().width();
|
||||
KDCoordinate height = bounds().height();
|
||||
KDColor backgroundColor = isHighlighted() ? Palette::ListCellBackgroundSelected : Palette::ListCellBackground;
|
||||
ctx->fillRect(KDRect(k_separatorThickness, k_separatorThickness, width-2*k_separatorThickness, height-k_separatorThickness), backgroundColor);
|
||||
// Draw rectangle around cell
|
||||
ctx->fillRect(KDRect(0, 0, width, k_separatorThickness), Palette::ListCellBorder);
|
||||
ctx->fillRect(KDRect(0, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), Palette::ListCellBorder);
|
||||
ctx->fillRect(KDRect(width-k_separatorThickness, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), Palette::ListCellBorder);
|
||||
ctx->fillRect(KDRect(0, height-k_separatorThickness, width, k_separatorThickness), Palette::ListCellBorder);
|
||||
}
|
||||
drawInnerRect(ctx, bounds(), backgroundColor);
|
||||
drawBorderOfRect(ctx, bounds(), Palette::ListCellBorder);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ const char * TableView::className() const {
|
||||
}
|
||||
#endif
|
||||
|
||||
void TableView::layoutSubviews() {
|
||||
void TableView::layoutSubviews(bool force) {
|
||||
/* On the one hand, ScrollView::layoutSubviews()
|
||||
* calls setFrame(...) over m_contentView,
|
||||
* which typically calls layoutSubviews() over m_contentView.
|
||||
@@ -49,8 +49,8 @@ void TableView::layoutSubviews() {
|
||||
* FIXME:
|
||||
* Finally, this solution is not optimal at all since
|
||||
* layoutSubviews is called twice over m_contentView. */
|
||||
m_contentView.layoutSubviews();
|
||||
ScrollView::layoutSubviews();
|
||||
m_contentView.layoutSubviews(force);
|
||||
ScrollView::layoutSubviews(force);
|
||||
}
|
||||
|
||||
void TableView::reloadCellAtLocation(int i, int j) {
|
||||
@@ -166,7 +166,7 @@ View * TableView::ContentView::subviewAtIndex(int index) {
|
||||
return m_dataSource->reusableCell(typeIndex, type);
|
||||
}
|
||||
|
||||
void TableView::ContentView::layoutSubviews() {
|
||||
void TableView::ContentView::layoutSubviews(bool force) {
|
||||
/* The number of subviews might change during the layouting so it needs to be
|
||||
* recomputed at each step of the for loop. */
|
||||
for (int index = 0; index < numberOfSubviews(); index++) {
|
||||
@@ -174,7 +174,7 @@ void TableView::ContentView::layoutSubviews() {
|
||||
int i = absoluteColumnNumberFromSubviewIndex(index);
|
||||
int j = absoluteRowNumberFromSubviewIndex(index);
|
||||
m_dataSource->willDisplayCellAtLocation((HighlightCell *)cell, i, j);
|
||||
cell->setFrame(cellFrame(i,j));
|
||||
cell->setFrame(cellFrame(i,j), force);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
#include <escher/text_input_helpers.h>
|
||||
#include <ion/unicode/utf8_decoder.h>
|
||||
#include <ion/unicode/utf8_helper.h>
|
||||
#include <poincare/serialization_helper.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
static inline const char * maxPointer(const char * x, const char * y) { return x > y ? x : y; }
|
||||
static inline const char * minPointer(const char * x, const char * y) { return x < y ? x : y; }
|
||||
static inline size_t minSizeT(size_t x, size_t y) { return x < y ? x : y; }
|
||||
|
||||
/* TextArea */
|
||||
|
||||
@@ -37,6 +40,12 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for
|
||||
if (*text == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the selected text if needed
|
||||
if (!contentView()->selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
}
|
||||
|
||||
/* Compute the indentation. If the text cannot be inserted with the
|
||||
* indentation, stop here. */
|
||||
int spacesCount = 0;
|
||||
@@ -96,7 +105,16 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for
|
||||
bool TextArea::handleEvent(Ion::Events::Event event) {
|
||||
if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) {
|
||||
return true;
|
||||
} else if (handleBoxEvent(event)) {
|
||||
}
|
||||
if (handleBoxEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight) {
|
||||
selectLeftRight(event == Ion::Events::ShiftLeft, false);
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) {
|
||||
selectUpDown(event == Ion::Events::ShiftUp);
|
||||
return true;
|
||||
} else if (event == Ion::Events::ShiftLeft) {
|
||||
contentView()->moveCursorGeo(-INT_MAX/2, 0);
|
||||
@@ -107,21 +125,70 @@ bool TextArea::handleEvent(Ion::Events::Event event) {
|
||||
} else if (event == Ion::Events::ShiftDown) {
|
||||
contentView()->moveCursorGeo(0, INT_MAX/2);
|
||||
} else if (event == Ion::Events::Left) {
|
||||
if (contentView()->resetSelection()) {
|
||||
return true;
|
||||
}
|
||||
return TextInput::moveCursorLeft();
|
||||
} else if (event == Ion::Events::Right) {
|
||||
}
|
||||
if (event == Ion::Events::Right) {
|
||||
if (contentView()->resetSelection()) {
|
||||
return true;
|
||||
}
|
||||
return TextInput::moveCursorRight();
|
||||
}
|
||||
|
||||
if (event.hasText()) {
|
||||
return handleEventWithText(event.text());
|
||||
}
|
||||
if (event == Ion::Events::EXE) {
|
||||
return handleEventWithText("\n");
|
||||
}
|
||||
if (event == Ion::Events::Copy || event == Ion::Events::Cut) {
|
||||
if (contentView()->selectionIsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const char * start = contentView()->selectionStart();
|
||||
Clipboard::sharedClipboard()->store(start, contentView()->selectionEnd() - start);
|
||||
if (event == Ion::Events::Cut) {
|
||||
deleteSelection();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Paste) {
|
||||
return handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
|
||||
}
|
||||
|
||||
// The following events need a scrollToCursor and return true
|
||||
if (event == Ion::Events::Backspace) {
|
||||
if (contentView()->selectionIsEmpty()) {
|
||||
if (!removePreviousGlyph()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
deleteSelection();
|
||||
return true;
|
||||
}
|
||||
} else if (event == Ion::Events::Up) {
|
||||
contentView()->resetSelection();
|
||||
contentView()->moveCursorGeo(0, -1);
|
||||
} else if (event == Ion::Events::Down) {
|
||||
contentView()->resetSelection();
|
||||
contentView()->moveCursorGeo(0, 1);
|
||||
<<<<<<< HEAD
|
||||
} else if (event == Ion::Events::Backspace) {
|
||||
return removePreviousGlyph();
|
||||
} else if (event == Ion::Events::EXE) {
|
||||
return handleEventWithText("\n");
|
||||
=======
|
||||
>>>>>>> upstream/master
|
||||
} else if (event == Ion::Events::Clear) {
|
||||
if (!contentView()->removeEndOfLine()) {
|
||||
if (!contentView()->selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
return true;
|
||||
} else if (!contentView()->removeEndOfLine()) {
|
||||
contentView()->removeStartOfLine();
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
} else if (event == Ion::Events::Paste) {
|
||||
return handleEventWithText(Clipboard::sharedClipboard()->storedText());
|
||||
} else if (event == Ion::Events::Percent) {
|
||||
@@ -132,6 +199,8 @@ bool TextArea::handleEvent(Ion::Events::Event event) {
|
||||
} else {
|
||||
return handleEventWithText(event.text());
|
||||
}
|
||||
=======
|
||||
>>>>>>> upstream/master
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -151,11 +220,11 @@ int TextArea::indentationBeforeCursor() const {
|
||||
* another code point, until reaching the beginning of the line. */
|
||||
UTF8Helper::PerformAtCodePoints(const_cast<TextArea *>(this)->contentView()->text(), ' ',
|
||||
[](int codePointOffset, void * indentationSize, int context1, int context2){
|
||||
int * castedSize = (int *) indentationSize;
|
||||
*castedSize = *castedSize + 1;
|
||||
int * castedSize = (int *) indentationSize;
|
||||
*castedSize = *castedSize + 1;
|
||||
},
|
||||
[](int codePointOffset, void * indentationSize, int context1, int context2){
|
||||
*((int *) indentationSize) = 0;
|
||||
*((int *) indentationSize) = 0;
|
||||
},
|
||||
&indentationSize, 0, -1, '\n', false, cursorLocation());
|
||||
return indentationSize;
|
||||
@@ -233,6 +302,31 @@ CodePoint TextArea::Text::removePreviousGlyph(char * * position) {
|
||||
return removedCodePoint;
|
||||
}
|
||||
|
||||
size_t TextArea::Text::removeText(const char * start, const char * end) {
|
||||
assert(start <= end);
|
||||
assert(start >= m_buffer && end <= m_buffer + m_bufferSize);
|
||||
|
||||
char * dst = const_cast<char* >(start);
|
||||
char * src = const_cast<char* >(end);
|
||||
size_t delta = src - dst;
|
||||
|
||||
if (delta == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t index = src - m_buffer; index < m_bufferSize; index++) {
|
||||
*dst = *src;
|
||||
if (*src == 0) {
|
||||
assert(delta > 0);
|
||||
return delta;
|
||||
}
|
||||
dst++;
|
||||
src++;
|
||||
}
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t TextArea::Text::removeRemainingLine(const char * location, int direction) {
|
||||
assert(m_buffer != nullptr);
|
||||
assert(location >= m_buffer && location <= m_buffer + m_bufferSize);
|
||||
@@ -258,24 +352,7 @@ size_t TextArea::Text::removeRemainingLine(const char * location, int direction)
|
||||
}
|
||||
}
|
||||
|
||||
char * dst = const_cast<char* >(direction > 0 ? location : codePointPosition);
|
||||
char * src = const_cast<char* >(direction > 0 ? codePointPosition : location);
|
||||
assert(src >= dst);
|
||||
|
||||
size_t delta = src - dst;
|
||||
if (delta == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (size_t index = src - m_buffer; index < m_bufferSize; index++) {
|
||||
*dst = *src;
|
||||
if (*src == 0) {
|
||||
return delta;
|
||||
}
|
||||
dst++;
|
||||
src++;
|
||||
}
|
||||
assert(false);
|
||||
return 0;
|
||||
return removeText(direction > 0 ? location : codePointPosition, direction > 0 ? codePointPosition : location);
|
||||
}
|
||||
|
||||
/* TextArea::Text::Line */
|
||||
@@ -351,22 +428,49 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
for (Text::Line line : m_text) {
|
||||
KDCoordinate width = line.glyphWidth(m_font);
|
||||
if (y >= topLeft.line() && y <= bottomRight.line() && topLeft.column() < (int)width) {
|
||||
drawLine(ctx, y, line.text(), line.charLength(), topLeft.column(), bottomRight.column());
|
||||
drawLine(ctx, y, line.text(), line.charLength(), topLeft.column(), bottomRight.column(), m_selectionStart, m_selectionEnd);
|
||||
}
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, size_t length, KDColor textColor, KDColor backgroundColor) const {
|
||||
void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const {
|
||||
if (length < 0) {
|
||||
return;
|
||||
}
|
||||
KDSize glyphSize = m_font->glyphSize();
|
||||
ctx->drawString(
|
||||
|
||||
bool drawSelection = selectionStart != nullptr && selectionEnd > text && selectionStart < text + length;
|
||||
KDPoint nextPoint = ctx->drawString(
|
||||
text,
|
||||
KDPoint(column*glyphSize.width(), line*glyphSize.height()),
|
||||
m_font,
|
||||
textColor,
|
||||
backgroundColor,
|
||||
length
|
||||
drawSelection ? (selectionStart >= text ? minSizeT(length, selectionStart - text) : 0) : length
|
||||
);
|
||||
if (!drawSelection) {
|
||||
return;
|
||||
}
|
||||
const char * highlightedDrawStart = maxPointer(selectionStart, text);
|
||||
size_t highlightedDrawLength = minSizeT(selectionEnd - highlightedDrawStart, length - (highlightedDrawStart - text));
|
||||
|
||||
nextPoint = ctx->drawString(
|
||||
highlightedDrawStart,
|
||||
nextPoint,
|
||||
m_font,
|
||||
textColor,
|
||||
backgroundHighlightColor,
|
||||
highlightedDrawLength);
|
||||
|
||||
const char * notHighlightedDrawStart = highlightedDrawStart + highlightedDrawLength;
|
||||
ctx->drawString(
|
||||
notHighlightedDrawStart,
|
||||
nextPoint,
|
||||
m_font,
|
||||
textColor,
|
||||
backgroundColor,
|
||||
length - (notHighlightedDrawStart - text));
|
||||
}
|
||||
|
||||
KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const {
|
||||
@@ -379,12 +483,12 @@ KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const {
|
||||
);
|
||||
}
|
||||
|
||||
void TextArea::TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) {
|
||||
void TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) {
|
||||
m_text.setText(textBuffer, textBufferSize);
|
||||
m_cursorLocation = text();
|
||||
}
|
||||
|
||||
bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, const char * location) {
|
||||
bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location) {
|
||||
int textSize = strlen(text);
|
||||
if (m_text.textLength() + textSize >= m_text.bufferSize() || textSize == 0) {
|
||||
return false;
|
||||
@@ -401,12 +505,14 @@ bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, co
|
||||
&lineBreak, 0);
|
||||
|
||||
assert(UTF8Helper::CodePointIs(nullLocation, 0));
|
||||
m_text.insertText(text, nullLocation - text, const_cast<char *>(location));
|
||||
m_text.insertText(text, nullLocation - text, location);
|
||||
// Replace System parentheses (used to keep layout tree structure) by normal parentheses
|
||||
Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, nullLocation - text);
|
||||
reloadRectFromPosition(location, lineBreak);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextArea::TextArea::ContentView::removePreviousGlyph() {
|
||||
bool TextArea::ContentView::removePreviousGlyph() {
|
||||
if (cursorLocation() <= text()) {
|
||||
assert(cursorLocation() == text());
|
||||
return false;
|
||||
@@ -445,6 +551,16 @@ bool TextArea::ContentView::removeStartOfLine() {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t TextArea::ContentView::deleteSelection() {
|
||||
assert(!selectionIsEmpty());
|
||||
size_t removedLength = m_text.removeText(m_selectionStart, m_selectionEnd);
|
||||
/* We cannot call resetSelection() because m_selectionStart and m_selectionEnd
|
||||
* are invalid */
|
||||
m_selectionStart = nullptr;
|
||||
m_selectionEnd = nullptr;
|
||||
return removedLength;
|
||||
}
|
||||
|
||||
KDRect TextArea::ContentView::glyphFrameAtPosition(const char * text, const char * position) const {
|
||||
assert(text == m_text.text());
|
||||
KDSize glyphSize = m_font->glyphSize();
|
||||
@@ -476,3 +592,11 @@ void TextArea::ContentView::moveCursorGeo(int deltaX, int deltaY) {
|
||||
Text::Position p = m_text.positionAtPointer(cursorLocation());
|
||||
setCursorLocation(m_text.pointerAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)));
|
||||
}
|
||||
|
||||
void TextArea::selectUpDown(bool up) {
|
||||
const char * previousCursorLocation = contentView()->cursorLocation();
|
||||
contentView()->moveCursorGeo(0, up ? -1 : 1);
|
||||
const char * newCursorLocation = contentView()->cursorLocation();
|
||||
contentView()->addSelection(up ? newCursorLocation : previousCursorLocation, up ? previousCursorLocation : newCursorLocation);
|
||||
scrollToCursor();
|
||||
}
|
||||
|
||||
@@ -12,14 +12,12 @@ static char s_draftTextBuffer[TextField::maxBufferSize()];
|
||||
/* TextField::ContentView */
|
||||
|
||||
TextField::ContentView::ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
|
||||
TextInput::ContentView(font),
|
||||
TextInput::ContentView(font, horizontalAlignment, verticalAlignment),
|
||||
m_isEditing(false),
|
||||
m_textBuffer(textBuffer),
|
||||
m_textBufferSize(textBufferSize),
|
||||
m_draftTextBufferSize(draftTextBufferSize),
|
||||
m_currentDraftTextLength(0),
|
||||
m_horizontalAlignment(horizontalAlignment),
|
||||
m_verticalAlignment(verticalAlignment),
|
||||
m_textColor(textColor),
|
||||
m_backgroundColor(backgroundColor)
|
||||
{
|
||||
@@ -47,7 +45,21 @@ void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
backgroundColor = Palette::BackgroundHard;
|
||||
}*/
|
||||
ctx->fillRect(bounds(), backgroundColor);
|
||||
ctx->drawString(text(), glyphFrameAtPosition(text(), text()).origin(), m_font, m_textColor, backgroundColor);
|
||||
if (selectionIsEmpty()) {
|
||||
ctx->drawString(text(), glyphFrameAtPosition(text(), text()).origin(), m_font, m_textColor, backgroundColor);
|
||||
} else {
|
||||
int selectionOffset = m_selectionStart - s_draftTextBuffer;
|
||||
const char * textToDraw = text();
|
||||
// Draw the non selected text on the left of the selection
|
||||
ctx->drawString(textToDraw, glyphFrameAtPosition(text(), text()).origin(), m_font, m_textColor, backgroundColor, selectionOffset);
|
||||
int selectionLength = m_selectionEnd - m_selectionStart;
|
||||
textToDraw += selectionOffset;
|
||||
// Draw the selected text
|
||||
ctx->drawString(text() + selectionOffset, glyphFrameAtPosition(text(), textToDraw).origin(), m_font, m_textColor, Palette::Select, selectionLength);
|
||||
textToDraw += selectionLength;
|
||||
// Draw the non selected text on the right of the selection
|
||||
ctx->drawString(text() + selectionOffset + selectionLength, glyphFrameAtPosition(text(), textToDraw).origin(), m_font, m_textColor, backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
const char * TextField::ContentView::text() const {
|
||||
@@ -76,16 +88,11 @@ void TextField::ContentView::setText(const char * text) {
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TextField::ContentView::setAlignment(float horizontalAlignment, float verticalAlignment) {
|
||||
m_horizontalAlignment = horizontalAlignment;
|
||||
m_verticalAlignment = verticalAlignment;
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TextField::ContentView::setEditing(bool isEditing) {
|
||||
if (m_isEditing == isEditing) {
|
||||
return;
|
||||
}
|
||||
resetSelection();
|
||||
m_isEditing = isEditing;
|
||||
m_currentDraftTextLength = strlen(s_draftTextBuffer);
|
||||
if (m_cursorLocation < s_draftTextBuffer
|
||||
@@ -106,39 +113,23 @@ void TextField::ContentView::reinitDraftTextBuffer() {
|
||||
setCursorLocation(s_draftTextBuffer);
|
||||
}
|
||||
|
||||
bool TextField::ContentView::insertTextAtLocation(const char * text, const char * location) {
|
||||
bool TextField::ContentView::insertTextAtLocation(const char * text, char * location) {
|
||||
assert(m_isEditing);
|
||||
|
||||
int textSize = strlen(text);
|
||||
if (m_currentDraftTextLength + textSize >= m_draftTextBufferSize || textSize == 0) {
|
||||
int textLength = strlen(text);
|
||||
if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memmove(const_cast<char *>(location + textSize), location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location);
|
||||
memmove(location + textLength, location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location);
|
||||
|
||||
// Caution! One byte will be overridden by the null-terminating char of strlcpy
|
||||
char * overridenByteLocation = const_cast<char *>(location + strlen(text));
|
||||
char * overridenByteLocation = location + textLength;
|
||||
char overridenByte = *overridenByteLocation;
|
||||
strlcpy(const_cast<char *>(location), text, (s_draftTextBuffer + m_draftTextBufferSize) - location);
|
||||
strlcpy(location, text, (s_draftTextBuffer + m_draftTextBufferSize) - location);
|
||||
assert(overridenByteLocation < s_draftTextBuffer + m_draftTextBufferSize);
|
||||
*overridenByteLocation = overridenByte;
|
||||
m_currentDraftTextLength += textSize;
|
||||
|
||||
UTF8Decoder decoder(s_draftTextBuffer);
|
||||
const char * codePointPointer = decoder.stringPosition();
|
||||
CodePoint codePoint = decoder.nextCodePoint();
|
||||
assert(!codePoint.isCombining());
|
||||
while (codePoint != UCodePointNull) {
|
||||
assert(codePointPointer < s_draftTextBuffer + m_draftTextBufferSize);
|
||||
if (codePoint == '\n') {
|
||||
assert(UTF8Decoder::CharSizeOfCodePoint('\n') == 1);
|
||||
*(const_cast<char *>(codePointPointer)) = 0;
|
||||
m_currentDraftTextLength = codePointPointer - s_draftTextBuffer;
|
||||
break;
|
||||
}
|
||||
codePointPointer = decoder.stringPosition();
|
||||
codePoint = decoder.nextCodePoint();
|
||||
}
|
||||
m_currentDraftTextLength += textLength;
|
||||
|
||||
reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : s_draftTextBuffer);
|
||||
return true;
|
||||
@@ -162,19 +153,19 @@ bool TextField::ContentView::removePreviousGlyph() {
|
||||
reloadRectFromPosition(s_draftTextBuffer);
|
||||
}
|
||||
// Remove the glyph if possible
|
||||
int removedSize = UTF8Helper::RemovePreviousGlyph(s_draftTextBuffer, const_cast<char *>(cursorLocation()));
|
||||
if (removedSize == 0) {
|
||||
int removedLength = UTF8Helper::RemovePreviousGlyph(s_draftTextBuffer, const_cast<char *>(cursorLocation()));
|
||||
if (removedLength == 0) {
|
||||
assert(cursorLocation() == s_draftTextBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the draft buffer length
|
||||
m_currentDraftTextLength-= removedSize;
|
||||
m_currentDraftTextLength-= removedLength;
|
||||
assert(s_draftTextBuffer[m_currentDraftTextLength] == 0);
|
||||
|
||||
// Set the cursor location and reload the view
|
||||
assert(cursorLocation() - removedSize >= s_draftTextBuffer);
|
||||
setCursorLocation(cursorLocation() - removedSize);
|
||||
assert(cursorLocation() - removedLength >= s_draftTextBuffer);
|
||||
setCursorLocation(cursorLocation() - removedLength);
|
||||
if (m_horizontalAlignment == 0.0f) {
|
||||
reloadRectFromPosition(cursorLocation());
|
||||
}
|
||||
@@ -209,12 +200,26 @@ void TextField::ContentView::didModifyTextBuffer() {
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
void TextField::ContentView::layoutSubviews() {
|
||||
size_t TextField::ContentView::deleteSelection() {
|
||||
assert(!selectionIsEmpty());
|
||||
assert(m_isEditing);
|
||||
size_t removedLength = m_selectionEnd - m_selectionStart;
|
||||
strlcpy(const_cast<char *>(m_selectionStart), m_selectionEnd, m_draftTextBufferSize - (m_selectionStart - s_draftTextBuffer));
|
||||
/* We cannot call resetSelection() because m_selectionStart and m_selectionEnd
|
||||
* are invalid */
|
||||
m_selectionStart = nullptr;
|
||||
m_selectionEnd = nullptr;
|
||||
assert(removedLength <= m_currentDraftTextLength);
|
||||
m_currentDraftTextLength -= removedLength;
|
||||
return removedLength;
|
||||
}
|
||||
|
||||
void TextField::ContentView::layoutSubviews(bool force) {
|
||||
if (!m_isEditing) {
|
||||
m_cursorView.setFrame(KDRectZero);
|
||||
m_cursorView.setFrame(KDRectZero, force);
|
||||
return;
|
||||
}
|
||||
TextInput::ContentView::layoutSubviews();
|
||||
TextInput::ContentView::layoutSubviews(force);
|
||||
}
|
||||
|
||||
KDRect TextField::ContentView::glyphFrameAtPosition(const char * buffer, const char * position) const {
|
||||
@@ -268,10 +273,6 @@ void TextField::setText(const char * text) {
|
||||
setCursorLocation(m_contentView.editedText()+strlen(text));
|
||||
}
|
||||
|
||||
void TextField::setAlignment(float horizontalAlignment, float verticalAlignment) {
|
||||
m_contentView.setAlignment(horizontalAlignment, verticalAlignment);
|
||||
}
|
||||
|
||||
bool TextField::privateHandleEvent(Ion::Events::Event event) {
|
||||
// Handle Toolbox or Var event
|
||||
if (handleBoxEvent(event)) {
|
||||
@@ -298,6 +299,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
|
||||
if (m_delegate->textFieldDidFinishEditing(this, m_contentView.editedText(), event)) {
|
||||
// Clean draft text for next use
|
||||
reinitDraftTextBuffer();
|
||||
resetSelection();
|
||||
/* We allow overscroll to avoid calling layoutSubviews twice because the
|
||||
* content might have changed. */
|
||||
reloadScroll(true);
|
||||
@@ -317,29 +319,36 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Backspace && isEditing()) {
|
||||
return removePreviousGlyph();
|
||||
if (m_contentView.selectionIsEmpty()) {
|
||||
return removePreviousGlyph();
|
||||
}
|
||||
deleteSelection();
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Back && isEditing()) {
|
||||
reinitDraftTextBuffer();
|
||||
resetSelection();
|
||||
setEditing(false);
|
||||
m_delegate->textFieldDidAbortEditing(this);
|
||||
reloadScroll(true);
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Clear && isEditing()) {
|
||||
if (!removeEndOfLine()) {
|
||||
if (!m_contentView.selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
} else if (!removeEndOfLine()) {
|
||||
removeWholeText();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Copy && !isEditing()) {
|
||||
Clipboard::sharedClipboard()->store(text());
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Cut && !isEditing()) {
|
||||
Clipboard::sharedClipboard()->store(text());
|
||||
reinitDraftTextBuffer();
|
||||
setEditing(true);
|
||||
if (event == Ion::Events::Copy || event == Ion::Events::Cut) {
|
||||
if (storeInClipboard() && event == Ion::Events::Cut) {
|
||||
if (!m_contentView.selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
} else {
|
||||
removeWholeText();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -404,12 +413,14 @@ bool TextField::handleEvent(Ion::Events::Event event) {
|
||||
bool didHandleEvent = false;
|
||||
if (privateHandleMoveEvent(event)) {
|
||||
didHandleEvent = true;
|
||||
} else if (privateHandleSelectEvent(event)) {
|
||||
didHandleEvent = true;
|
||||
} else if (m_delegate->textFieldDidReceiveEvent(this, event)) {
|
||||
return true;
|
||||
} else if (event.hasText()) {
|
||||
return handleEventWithText(event.text());
|
||||
} else if (event == Ion::Events::Paste) {
|
||||
return handleEventWithText(Clipboard::sharedClipboard()->storedText());
|
||||
return handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true);
|
||||
} else if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !isEditing()) {
|
||||
const char * previousText = m_contentView.text();
|
||||
setEditing(true);
|
||||
@@ -429,22 +440,41 @@ void TextField::scrollToCursor() {
|
||||
return TextInput::scrollToCursor();
|
||||
}
|
||||
|
||||
bool TextField::shouldFinishEditing(Ion::Events::Event event) {
|
||||
if (m_delegate->textFieldShouldFinishEditing(this, event)) {
|
||||
resetSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextField::privateHandleMoveEvent(Ion::Events::Event event) {
|
||||
if (!isEditing()) {
|
||||
return false;
|
||||
}
|
||||
const char * draftBuffer = m_contentView.editedText();
|
||||
if (event == Ion::Events::Left && cursorLocation() > draftBuffer) {
|
||||
return TextInput::moveCursorLeft();
|
||||
if (event == Ion::Events::Left || event == Ion::Events::Right) {
|
||||
if (!m_contentView.selectionIsEmpty()) {
|
||||
resetSelection();
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Left && cursorLocation() > draftBuffer) {
|
||||
return TextInput::moveCursorLeft();
|
||||
}
|
||||
if (event == Ion::Events::Right && cursorLocation() < draftBuffer + draftTextLength()) {
|
||||
return TextInput::moveCursorRight();
|
||||
}
|
||||
}
|
||||
if (event == Ion::Events::Right && cursorLocation() < draftBuffer + draftTextLength()) {
|
||||
return TextInput::moveCursorRight();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextField::privateHandleSelectEvent(Ion::Events::Event event) {
|
||||
if (!isEditing()) {
|
||||
return false;
|
||||
}
|
||||
if (event == Ion::Events::ShiftLeft) {
|
||||
return setCursorLocation(draftBuffer);
|
||||
}
|
||||
if (event == Ion::Events::ShiftRight) {
|
||||
return setCursorLocation(draftBuffer + draftTextLength());
|
||||
if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight || event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) {
|
||||
selectLeftRight(event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftUp, event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -459,39 +489,60 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo
|
||||
|
||||
assert(isEditing());
|
||||
|
||||
if (eventText[0] == 0) {
|
||||
/* For instance, the event might be EXE on a non-editing text field to start
|
||||
* edition. */
|
||||
setCursorLocation(m_contentView.editedText());
|
||||
return m_delegate->textFieldDidHandleEvent(this, true, previousTextLength != 0);
|
||||
// Delete the selected text if needed
|
||||
if (!contentView()->selectionIsEmpty()) {
|
||||
deleteSelection();
|
||||
}
|
||||
|
||||
// Remove the Empty code points
|
||||
constexpr int bufferSize = TextField::maxBufferSize();
|
||||
char buffer[bufferSize];
|
||||
UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, eventText, UCodePointEmpty);
|
||||
if (eventText[0] != 0) {
|
||||
// Remove the Empty code points
|
||||
constexpr int bufferSize = TextField::maxBufferSize();
|
||||
char buffer[bufferSize];
|
||||
{
|
||||
CodePoint c[] = {UCodePointEmpty, '\n'};
|
||||
bool complete = UTF8Helper::CopyAndRemoveCodePoints(buffer, bufferSize, eventText, c, 2);
|
||||
/* If the text is too long to be stored in buffer, we do not insert any
|
||||
* text. This behaviour is consistent with 'insertTextAtLocation'
|
||||
* behaviour. */
|
||||
if (!complete) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Replace System parentheses (used to keep layout tree structure) by normal parentheses
|
||||
Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer);
|
||||
|
||||
// Replace System parentheses (used to keep layout tree structure) by normal parentheses
|
||||
Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer);
|
||||
|
||||
const char * nextCursorLocation = m_contentView.editedText() + draftTextLength();
|
||||
if (insertTextAtLocation(buffer, cursorLocation())) {
|
||||
/* The cursor position depends on the text as we sometimes want to position
|
||||
* the cursor at the end of the text and sometimes after the first
|
||||
* parenthesis. */
|
||||
nextCursorLocation = cursorLocation();
|
||||
if (forceCursorRightOfText) {
|
||||
nextCursorLocation+= strlen(buffer);
|
||||
} else {
|
||||
nextCursorLocation+= TextInputHelpers::CursorPositionInCommand(eventText) - eventText;
|
||||
if (insertTextAtLocation(buffer, const_cast<char *>(cursorLocation()))) {
|
||||
/* The cursor position depends on the text as we sometimes want to position
|
||||
* the cursor at the end of the text and sometimes after the first
|
||||
* parenthesis. */
|
||||
const char * nextCursorLocation = cursorLocation();
|
||||
if (forceCursorRightOfText) {
|
||||
nextCursorLocation+= strlen(buffer);
|
||||
} else {
|
||||
nextCursorLocation+= TextInputHelpers::CursorPositionInCommand(eventText) - eventText;
|
||||
}
|
||||
setCursorLocation(nextCursorLocation);
|
||||
}
|
||||
}
|
||||
setCursorLocation(nextCursorLocation);
|
||||
return m_delegate->textFieldDidHandleEvent(this, true, strlen(text()) != previousTextLength);
|
||||
}
|
||||
|
||||
void TextField::removeWholeText() {
|
||||
reinitDraftTextBuffer();
|
||||
setEditing(true);
|
||||
resetSelection();
|
||||
markRectAsDirty(bounds());
|
||||
layoutSubviews();
|
||||
reloadScroll();
|
||||
}
|
||||
|
||||
bool TextField::storeInClipboard() const {
|
||||
if (!isEditing()) {
|
||||
Clipboard::sharedClipboard()->store(text());
|
||||
return true;
|
||||
} else if (!m_contentView.selectionIsEmpty()) {
|
||||
const char * start = m_contentView.selectionStart();
|
||||
Clipboard::sharedClipboard()->store(start, m_contentView.selectionEnd() - start);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
static inline const char * minCharPointer(const char * x, const char * y) { return x < y ? x : y; }
|
||||
static inline const char * maxCharPointer(const char * x, const char * y) { return x > y ? x : y; }
|
||||
|
||||
void TextInput::ContentView::setFont(const KDFont * font) {
|
||||
m_font = font;
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TextInput::ContentView::setCursorLocation(const char * location) {
|
||||
assert(location != nullptr);
|
||||
assert(location >= editedText());
|
||||
@@ -16,37 +21,103 @@ void TextInput::ContentView::setCursorLocation(const char * location) {
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
void TextInput::ContentView::setFont(const KDFont * font) {
|
||||
m_font = font;
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
KDRect TextInput::ContentView::cursorRect() {
|
||||
return glyphFrameAtPosition(editedText(), m_cursorLocation);
|
||||
}
|
||||
|
||||
void TextInput::ContentView::layoutSubviews() {
|
||||
m_cursorView.setFrame(cursorRect());
|
||||
}
|
||||
|
||||
void TextInput::ContentView::reloadRectFromPosition(const char * position, bool lineBreak) {
|
||||
markRectAsDirty(dirtyRectFromPosition(position, lineBreak));
|
||||
}
|
||||
|
||||
KDRect TextInput::ContentView::dirtyRectFromPosition(const char * position, bool lineBreak) const {
|
||||
KDRect glyphRect = glyphFrameAtPosition(text(), position);
|
||||
KDRect dirtyRect = KDRect(
|
||||
glyphRect.x(),
|
||||
glyphRect.y(),
|
||||
bounds().width() - glyphRect.x(),
|
||||
glyphRect.height());
|
||||
if (lineBreak) {
|
||||
dirtyRect = dirtyRect.unionedWith(
|
||||
KDRect(0,
|
||||
glyphRect.bottom() + 1,
|
||||
bounds().width(),
|
||||
bounds().height() - glyphRect.bottom() - 1));
|
||||
void TextInput::ContentView::addSelection(const char * left, const char * right) {
|
||||
bool emptySelection = selectionIsEmpty();
|
||||
if (emptySelection) {
|
||||
m_selectionStart = left;
|
||||
m_selectionEnd = right;
|
||||
} else if (left == m_selectionEnd) {
|
||||
m_selectionEnd = right;
|
||||
} else if (right == m_selectionStart) {
|
||||
m_selectionStart = left;
|
||||
} else if (right == m_selectionEnd) {
|
||||
if (left >= m_selectionStart) {
|
||||
m_selectionEnd = left;
|
||||
} else {
|
||||
m_selectionEnd = m_selectionStart;
|
||||
m_selectionStart = left;
|
||||
}
|
||||
} else {
|
||||
assert(left == m_selectionStart);
|
||||
if (right <= m_selectionEnd) {
|
||||
m_selectionStart = right;
|
||||
} else {
|
||||
m_selectionStart = m_selectionEnd;
|
||||
m_selectionEnd = right;
|
||||
}
|
||||
}
|
||||
reloadRectFromAndToPositions(left, right);
|
||||
if (m_selectionStart == m_selectionEnd) {
|
||||
m_selectionStart = nullptr;
|
||||
m_selectionEnd = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool TextInput::ContentView::resetSelection() {
|
||||
if (selectionIsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const char * previousStart = m_selectionStart;
|
||||
const char * previousEnd = m_selectionEnd;
|
||||
m_selectionStart = nullptr;
|
||||
m_selectionEnd = nullptr;
|
||||
reloadRectFromAndToPositions(previousStart, previousEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextInput::ContentView::selectionIsEmpty() const {
|
||||
assert(m_selectionStart != nullptr || m_selectionEnd == nullptr);
|
||||
assert(m_selectionEnd != nullptr || m_selectionStart == nullptr);
|
||||
return m_selectionStart == nullptr;
|
||||
}
|
||||
|
||||
void TextInput::ContentView::setAlignment(float horizontalAlignment, float verticalAlignment) {
|
||||
m_horizontalAlignment = horizontalAlignment;
|
||||
m_verticalAlignment = verticalAlignment;
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TextInput::ContentView::reloadRectFromPosition(const char * position, bool includeFollowingLines) {
|
||||
markRectAsDirty(dirtyRectFromPosition(position, includeFollowingLines));
|
||||
}
|
||||
|
||||
void TextInput::ContentView::layoutSubviews(bool force) {
|
||||
m_cursorView.setFrame(cursorRect(), force);
|
||||
}
|
||||
|
||||
void TextInput::ContentView::reloadRectFromAndToPositions(const char * start, const char * end) {
|
||||
if (start == end) {
|
||||
return;
|
||||
}
|
||||
KDRect startFrame = glyphFrameAtPosition(text(), start);
|
||||
KDRect endFrame = glyphFrameAtPosition(text(), end);
|
||||
bool onSameLine = startFrame.y() == endFrame.y();
|
||||
markRectAsDirty(KDRect(
|
||||
onSameLine ? startFrame.x() : 0,
|
||||
startFrame.y(),
|
||||
onSameLine ? endFrame.right() - startFrame.left() : bounds().width(),
|
||||
endFrame.bottom() - startFrame.top() + 1));
|
||||
}
|
||||
|
||||
KDRect TextInput::ContentView::dirtyRectFromPosition(const char * position, bool includeFollowingLines) const {
|
||||
KDRect glyphRect = glyphFrameAtPosition(text(), position);
|
||||
if (!includeFollowingLines) {
|
||||
KDRect dirtyRect = KDRect(
|
||||
glyphRect.x(),
|
||||
glyphRect.y(),
|
||||
bounds().width() - glyphRect.x(),
|
||||
glyphRect.height());
|
||||
return dirtyRect;
|
||||
}
|
||||
KDRect dirtyRect = KDRect(
|
||||
0,
|
||||
glyphRect.y(),
|
||||
bounds().width(),
|
||||
bounds().height() - glyphRect.y());
|
||||
return dirtyRect;
|
||||
}
|
||||
|
||||
@@ -58,6 +129,15 @@ bool TextInput::removePreviousGlyph() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextInput::setCursorLocation(const char * location) {
|
||||
assert(location != nullptr);
|
||||
const char * adjustedLocation = maxCharPointer(location, text());
|
||||
willSetCursorLocation(&adjustedLocation);
|
||||
contentView()->setCursorLocation(adjustedLocation);
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextInput::scrollToCursor() {
|
||||
/* Technically, we do not need to overscroll in text input. However, we should
|
||||
* layout the scroll view before calling scrollToContentRect (in case the size
|
||||
@@ -70,16 +150,27 @@ void TextInput::scrollToCursor() {
|
||||
scrollToContentRect(contentView()->cursorRect(), true);
|
||||
}
|
||||
|
||||
bool TextInput::setCursorLocation(const char * location) {
|
||||
assert(location != nullptr);
|
||||
const char * adjustedLocation = maxCharPointer(location, text());
|
||||
willSetCursorLocation(&adjustedLocation);
|
||||
contentView()->setCursorLocation(adjustedLocation);
|
||||
void TextInput::deleteSelection() {
|
||||
ContentView * cv = contentView();
|
||||
assert(!cv->selectionIsEmpty());
|
||||
const float horizontalAlignment = cv->horizontalAlignment();
|
||||
if (horizontalAlignment == 0.0f) {
|
||||
cv->reloadRectFromPosition(cv->selectionStart(), true);
|
||||
}
|
||||
bool cursorIsAtEndOfSelection = cv->selectionEnd() == cv->cursorLocation();
|
||||
size_t removedLength = cv->deleteSelection();
|
||||
if (cursorIsAtEndOfSelection) {
|
||||
setCursorLocation(cv->cursorLocation() - removedLength);
|
||||
}
|
||||
layoutSubviews(true); // Set the cursor frame by calling the subviews relayouting
|
||||
scrollToCursor();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextInput::insertTextAtLocation(const char * text, const char * location) {
|
||||
void TextInput::setAlignment(float horizontalAlignment, float verticalAlignment) {
|
||||
contentView()->setAlignment(horizontalAlignment, verticalAlignment);
|
||||
}
|
||||
|
||||
bool TextInput::insertTextAtLocation(const char * text, char * location) {
|
||||
if (contentView()->insertTextAtLocation(text, location)) {
|
||||
/* We layout the scrollable view before scrolling to cursor because the
|
||||
* content size might have changed. */
|
||||
@@ -115,6 +206,28 @@ bool TextInput::moveCursorRight() {
|
||||
return setCursorLocation(decoder.nextGlyphPosition());
|
||||
}
|
||||
|
||||
bool TextInput::selectLeftRight(bool left, bool all) {
|
||||
const char * cursorLoc = cursorLocation();
|
||||
const char * nextCursorLoc = nullptr;
|
||||
if (!all) {
|
||||
bool moved = left ? moveCursorLeft() : moveCursorRight();
|
||||
if (!moved) {
|
||||
return false;
|
||||
}
|
||||
nextCursorLoc = cursorLocation();
|
||||
} else {
|
||||
const char * t = text();
|
||||
nextCursorLoc = left ? t : t + strlen(t);
|
||||
willSetCursorLocation(&nextCursorLoc);
|
||||
if (cursorLoc == nextCursorLoc) {
|
||||
return false;
|
||||
}
|
||||
setCursorLocation(nextCursorLoc);
|
||||
}
|
||||
contentView()->addSelection(left ? nextCursorLoc : cursorLoc, left ? cursorLoc : nextCursorLoc);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextInput::privateRemoveEndOfLine() {
|
||||
return contentView()->removeEndOfLine();
|
||||
}
|
||||
|
||||
@@ -3,18 +3,6 @@ extern "C" {
|
||||
}
|
||||
#include <escher/view.h>
|
||||
|
||||
View::View() :
|
||||
m_frame(KDRectZero),
|
||||
m_superview(nullptr),
|
||||
m_dirtyRect(KDRectZero)
|
||||
{
|
||||
}
|
||||
|
||||
void View::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
// By default, a view doesn't do anything
|
||||
// It's transparent!
|
||||
}
|
||||
|
||||
const Window * View::window() const {
|
||||
if (m_superview == nullptr) {
|
||||
return nullptr;
|
||||
@@ -105,11 +93,11 @@ View * View::subview(int index) {
|
||||
}
|
||||
|
||||
void View::setSize(KDSize size) {
|
||||
setFrame(KDRect(m_frame.origin(), size));
|
||||
setFrame(KDRect(m_frame.origin(), size), false);
|
||||
}
|
||||
|
||||
void View::setFrame(KDRect frame) {
|
||||
if (frame == m_frame) {
|
||||
void View::setFrame(KDRect frame, bool force) {
|
||||
if (frame == m_frame && !force) {
|
||||
return;
|
||||
}
|
||||
/* CAUTION: This code is not resilient to multiple consecutive setFrame()
|
||||
@@ -133,7 +121,7 @@ void View::setFrame(KDRect frame) {
|
||||
// FIXME: m_dirtyRect = bounds(); would be more correct (in case the view is being shrinked)
|
||||
|
||||
if (!m_frame.isEmpty()) {
|
||||
layoutSubviews();
|
||||
layoutSubviews(force);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,14 +155,6 @@ KDRect View::absoluteVisibleFrame() const {
|
||||
}
|
||||
}
|
||||
|
||||
KDSize View::minimalSizeForOptimalDisplay() const {
|
||||
return KDSizeZero;
|
||||
}
|
||||
|
||||
void View::layoutSubviews() {
|
||||
}
|
||||
|
||||
|
||||
#if ESCHER_VIEW_LOGGING
|
||||
const char * View::className() const {
|
||||
return "View";
|
||||
|
||||
@@ -25,18 +25,18 @@ View * WarningController::ContentView::subviewAtIndex(int index) {
|
||||
return views[index];
|
||||
}
|
||||
|
||||
void WarningController::ContentView::layoutSubviews() {
|
||||
void WarningController::ContentView::layoutSubviews(bool force) {
|
||||
if (numberOfSubviews() == 1) {
|
||||
m_textView1.setFrame(bounds());
|
||||
m_textView1.setFrame(bounds(), force);
|
||||
m_textView1.setAlignment(k_middleAlignment, k_middleAlignment);
|
||||
return;
|
||||
}
|
||||
assert(numberOfSubviews() == 2);
|
||||
KDRect fullBounds = bounds();
|
||||
KDCoordinate halfHeight = fullBounds.height()/2;
|
||||
m_textView1.setFrame(KDRect(fullBounds.topLeft(), fullBounds.width(), halfHeight));
|
||||
m_textView1.setFrame(KDRect(fullBounds.topLeft(), fullBounds.width(), halfHeight), force);
|
||||
m_textView1.setAlignment(k_middleAlignment, k_shiftedAlignment);
|
||||
m_textView2.setFrame(KDRect(fullBounds.left(), fullBounds.top()+halfHeight, fullBounds.width(), halfHeight));
|
||||
m_textView2.setFrame(KDRect(fullBounds.left(), fullBounds.top()+halfHeight, fullBounds.width(), halfHeight), force);
|
||||
}
|
||||
|
||||
KDSize WarningController::ContentView::minimalSizeForOptimalDisplay() const {
|
||||
|
||||
@@ -31,9 +31,9 @@ View * Window::subviewAtIndex(int index) {
|
||||
return m_contentView;
|
||||
}
|
||||
|
||||
void Window::layoutSubviews() {
|
||||
void Window::layoutSubviews(bool force) {
|
||||
if (m_contentView != nullptr) {
|
||||
m_contentView->setFrame(bounds());
|
||||
m_contentView->setFrame(bounds(), force);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
235
escher/test/layout_field.cpp
Normal file
235
escher/test/layout_field.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#include <quiz.h>
|
||||
#include <escher.h>
|
||||
#include <assert.h>
|
||||
|
||||
void assert_events_lead_to_selection(const Ion::Events::Event * events, int numberOfEvents, const char * selectedParsedAndSerializedText) {
|
||||
LayoutField field = LayoutField(nullptr, nullptr);
|
||||
field.setEditing(true);
|
||||
for (int i = 0; i < numberOfEvents; i++) {
|
||||
field.handleEvent(events[i]);
|
||||
}
|
||||
Clipboard::sharedClipboard()->reset();
|
||||
field.handleEvent(Ion::Events::Copy);
|
||||
quiz_assert(strcmp(Clipboard::sharedClipboard()->storedText(), selectedParsedAndSerializedText) == 0);
|
||||
}
|
||||
|
||||
QUIZ_CASE(escher_layout_field_select_left_right) {
|
||||
// 123
|
||||
{
|
||||
// Select in a simple horizontal layout towards the left
|
||||
const int eventsCount = 6;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::ShiftLeft, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft};
|
||||
assert_events_lead_to_selection(events, eventsCount - 2, "3");
|
||||
assert_events_lead_to_selection(events, eventsCount - 1, "23");
|
||||
assert_events_lead_to_selection(events, eventsCount, "123");
|
||||
}
|
||||
{
|
||||
// Select in a simple horizontal layout towards the right
|
||||
const int eventsCount = 9;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::ShiftRight, Ion::Events::ShiftRight, Ion::Events::ShiftRight};
|
||||
assert_events_lead_to_selection(events, eventsCount - 2, "1");
|
||||
assert_events_lead_to_selection(events, eventsCount - 1, "12");
|
||||
assert_events_lead_to_selection(events, eventsCount, "123");
|
||||
}
|
||||
{
|
||||
// Select in a simple horizontal layout left then right
|
||||
const int eventsCount = 7;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Left, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftRight};
|
||||
assert_events_lead_to_selection(events, eventsCount, "2");
|
||||
}
|
||||
{
|
||||
/* Select in a simple horizontal layout towards the right and hit the end of
|
||||
* layout. */
|
||||
const int eventsCount = 11;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::ShiftRight, Ion::Events::ShiftRight, Ion::Events::ShiftRight,
|
||||
Ion::Events::ShiftRight, Ion::Events::ShiftRight};
|
||||
assert_events_lead_to_selection(events, eventsCount, "123");
|
||||
}
|
||||
{
|
||||
/* Select in a simple horizontal layout towards the right, hit the end of
|
||||
* layout and select left. */
|
||||
const int eventsCount = 11;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::ShiftRight, Ion::Events::ShiftRight, Ion::Events::ShiftRight,
|
||||
Ion::Events::ShiftRight, Ion::Events::ShiftLeft};
|
||||
assert_events_lead_to_selection(events, eventsCount, "12");
|
||||
}
|
||||
|
||||
/* g34
|
||||
* 1+---+78
|
||||
* h56 */
|
||||
{
|
||||
/* Select in a horizontal layout with a fraction, starting outside the
|
||||
* fraction. */
|
||||
const int eventsCount = 17;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Plus, Ion::Events::LowerG,
|
||||
Ion::Events::Three, Ion::Events::Four, Ion::Events::Division,
|
||||
Ion::Events::LowerH, Ion::Events::Five, Ion::Events::Six,
|
||||
Ion::Events::Right, Ion::Events::Plus, Ion::Events::Seven,
|
||||
Ion::Events::Eight, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftLeft, Ion::Events::ShiftLeft};
|
||||
assert_events_lead_to_selection(events, eventsCount, "\x12\x12g34\x13/\x12h56\x13\x13+78");
|
||||
}
|
||||
{
|
||||
/* Select in a horizontal layout with a fraction, starting inside the
|
||||
* fraction. */
|
||||
const int eventsCount = 21;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Plus, Ion::Events::LowerG,
|
||||
Ion::Events::Three, Ion::Events::Four, Ion::Events::Division,
|
||||
Ion::Events::LowerH, Ion::Events::Five, Ion::Events::Six,
|
||||
Ion::Events::Right, Ion::Events::Plus, Ion::Events::Seven,
|
||||
Ion::Events::Eight, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::ShiftLeft, Ion::Events::ShiftLeft, Ion::Events::ShiftLeft,
|
||||
};
|
||||
assert_events_lead_to_selection(events, eventsCount, "g34/h56");
|
||||
}
|
||||
}
|
||||
|
||||
QUIZ_CASE(escher_layout_field_select_up_down) {
|
||||
// 12
|
||||
{
|
||||
// Select in a simple horizontal layout up
|
||||
const int eventsCount = 5;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftUp};
|
||||
assert_events_lead_to_selection(events, eventsCount, "2");
|
||||
}
|
||||
{
|
||||
// Select in a simple horizontal layout up
|
||||
const int eventsCount = 5;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftDown};
|
||||
assert_events_lead_to_selection(events, eventsCount, "2");
|
||||
}
|
||||
/* 123
|
||||
* ---
|
||||
* 456 */
|
||||
{
|
||||
// Select up in a fraction
|
||||
const int eventsCount = 11;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
|
||||
Ion::Events::Six, Ion::Events::Left, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftUp, Ion::Events::ShiftRight};
|
||||
assert_events_lead_to_selection(events, eventsCount - 1, "123/456");
|
||||
assert_events_lead_to_selection(events, eventsCount, "");
|
||||
}
|
||||
{
|
||||
// Select down in a fraction
|
||||
const int eventsCount = 12;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
|
||||
Ion::Events::Six, Ion::Events::Left, Ion::Events::Up,
|
||||
Ion::Events::ShiftLeft, Ion::Events::ShiftDown, Ion::Events::ShiftLeft};
|
||||
assert_events_lead_to_selection(events, eventsCount - 1, "123/456");
|
||||
assert_events_lead_to_selection(events, eventsCount, "");
|
||||
}
|
||||
/* 123
|
||||
* 89+---
|
||||
* 456 */
|
||||
{
|
||||
// Select up next to a fraction (cursor on fraction)
|
||||
const int eventsCount = 15;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::Eight, Ion::Events::Nine, Ion::Events::Plus,
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
|
||||
Ion::Events::Six, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::Left, Ion::Events::Left, Ion::Events::ShiftUp};
|
||||
assert_events_lead_to_selection(events, eventsCount, "");
|
||||
}
|
||||
{
|
||||
// Select down next to a fraction (cursor not on the fraction)
|
||||
const int eventsCount = 17;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::Eight, Ion::Events::Nine, Ion::Events::Plus,
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Division, Ion::Events::Four, Ion::Events::Five,
|
||||
Ion::Events::Six, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::Left, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::Right, Ion::Events::ShiftDown};
|
||||
assert_events_lead_to_selection(events, eventsCount, "");
|
||||
}
|
||||
/* 12345
|
||||
* -----
|
||||
* ghi
|
||||
* ----
|
||||
* k|lm */
|
||||
{
|
||||
// Select up next to a fraction (cursor on fraction)
|
||||
const int eventsCount = 17;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Four, Ion::Events::Five, Ion::Events::Division,
|
||||
Ion::Events::LowerG, Ion::Events::LowerH, Ion::Events::LowerI,
|
||||
Ion::Events::Division, Ion::Events::LowerK, Ion::Events::LowerL,
|
||||
Ion::Events::LowerM, Ion::Events::Left, Ion::Events::Left,
|
||||
Ion::Events::ShiftUp, Ion::Events::ShiftUp
|
||||
|
||||
};
|
||||
assert_events_lead_to_selection(events, eventsCount - 1, "ghi/klm");
|
||||
assert_events_lead_to_selection(events, eventsCount, "12345/\x12ghi/klm\x13");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QUIZ_CASE(escher_layout_field_unselect) {
|
||||
// 1234
|
||||
{
|
||||
// Move left after selecting
|
||||
const int eventsCount = 8;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Four, Ion::Events::ShiftLeft, Ion::Events::Left,
|
||||
Ion::Events::Left, Ion::Events::ShiftRight};
|
||||
assert_events_lead_to_selection(events, eventsCount, "3");
|
||||
}
|
||||
}
|
||||
|
||||
QUIZ_CASE(escher_layout_field_delete) {
|
||||
// 1234
|
||||
{
|
||||
// Delete the selection
|
||||
const int eventsCount = 17;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Four, Ion::Events::Left, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftLeft, Ion::Events::Backspace, Ion::Events::Left,
|
||||
Ion::Events::ShiftRight, Ion::Events::ShiftRight
|
||||
};
|
||||
assert_events_lead_to_selection(events, eventsCount, "14");
|
||||
}
|
||||
{
|
||||
// Delete the by adding text
|
||||
const int eventsCount = 19;
|
||||
Ion::Events::Event events[eventsCount] = {
|
||||
Ion::Events::One, Ion::Events::Two, Ion::Events::Three,
|
||||
Ion::Events::Four, Ion::Events::Left, Ion::Events::ShiftLeft,
|
||||
Ion::Events::ShiftLeft, Ion::Events::Zero, Ion::Events::Left,
|
||||
Ion::Events::Left, Ion::Events::ShiftRight, Ion::Events::ShiftRight,
|
||||
Ion::Events::ShiftRight
|
||||
};
|
||||
assert_events_lead_to_selection(events, eventsCount, "104");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user