aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cru/common/String.hpp28
-rw-r--r--include/cru/osx/graphics/quartz/TextLayout.hpp18
-rw-r--r--include/cru/platform/graphics/TextLayout.hpp5
-rw-r--r--include/cru/ui/render/TextRenderObject.hpp3
-rw-r--r--src/osx/graphics/quartz/TextLayout.cpp191
-rw-r--r--src/ui/controls/TextBox.cpp1
-rw-r--r--src/ui/render/TextRenderObject.cpp6
7 files changed, 197 insertions, 55 deletions
diff --git a/include/cru/common/String.hpp b/include/cru/common/String.hpp
index 264a44ff..4dab5b5a 100644
--- a/include/cru/common/String.hpp
+++ b/include/cru/common/String.hpp
@@ -132,18 +132,20 @@ class CRU_BASE_API String {
const_iterator end() const { return this->buffer_ + this->size_; }
const_iterator cend() const { return this->buffer_ + this->size_; }
- reverse_iterator rbegin() { return reverse_iterator{begin()}; }
+ reverse_iterator rbegin() { return reverse_iterator{end()}; }
const_reverse_iterator rbegin() const {
- return const_reverse_iterator{begin()};
+ return const_reverse_iterator{end()};
}
const_reverse_iterator crbegin() const {
- return const_reverse_iterator{cbegin()};
+ return const_reverse_iterator{cend()};
}
- reverse_iterator rend() { return reverse_iterator{end()}; }
- const_reverse_iterator rend() const { return const_reverse_iterator{end()}; }
+ reverse_iterator rend() { return reverse_iterator{begin()}; }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator{begin()};
+ }
const_reverse_iterator crend() const {
- return const_reverse_iterator{cend()};
+ return const_reverse_iterator{cbegin()};
}
public:
@@ -246,18 +248,20 @@ class CRU_BASE_API StringView {
const_iterator end() const { return this->ptr_ + this->size_; }
const_iterator cend() const { return this->ptr_ + this->size_; }
- reverse_iterator rbegin() { return reverse_iterator{begin()}; }
+ reverse_iterator rbegin() { return reverse_iterator{end()}; }
const_reverse_iterator rbegin() const {
- return const_reverse_iterator{begin()};
+ return const_reverse_iterator{end()};
}
const_reverse_iterator crbegin() const {
- return const_reverse_iterator{cbegin()};
+ return const_reverse_iterator{cend()};
}
- reverse_iterator rend() { return reverse_iterator{end()}; }
- const_reverse_iterator rend() const { return const_reverse_iterator{end()}; }
+ reverse_iterator rend() { return reverse_iterator{begin()}; }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator{begin()};
+ }
const_reverse_iterator crend() const {
- return const_reverse_iterator{cend()};
+ return const_reverse_iterator{cbegin()};
}
StringView substr(Index pos);
diff --git a/include/cru/osx/graphics/quartz/TextLayout.hpp b/include/cru/osx/graphics/quartz/TextLayout.hpp
index 35bcc32d..b9bf5e44 100644
--- a/include/cru/osx/graphics/quartz/TextLayout.hpp
+++ b/include/cru/osx/graphics/quartz/TextLayout.hpp
@@ -28,6 +28,9 @@ class OsxCTTextLayout : public OsxQuartzResource, public virtual ITextLayout {
void SetMaxWidth(float max_width) override;
void SetMaxHeight(float max_height) override;
+ bool IsEditMode() override;
+ void SetEditMode(bool enable) override;
+
Rect GetTextBounds(bool includingTrailingSpace = false) override;
std::vector<Rect> TextRangeRect(const TextRange& text_range) override;
Rect TextSinglePoint(Index position, bool trailing) override;
@@ -45,17 +48,22 @@ class OsxCTTextLayout : public OsxQuartzResource, public virtual ITextLayout {
void ReleaseResource();
void RecreateFrame();
- Rect DoGetTextBounds(bool includingTrailingSpace = false);
- std::vector<Rect> DoTextRangeRect(const TextRange& text_range);
- Rect DoTextSinglePoint(Index position, bool trailing);
+ CGRect DoGetTextBounds(bool includingTrailingSpace = false);
+ CGRect DoGetTextBoundsIncludingEmptyLines(
+ bool includingTrailingSpace = false);
+ std::vector<CGRect> DoTextRangeRect(const TextRange& text_range);
+ CGRect DoTextSinglePoint(Index position, bool trailing);
private:
float max_width_;
float max_height_;
+ bool edit_mode_;
+
std::shared_ptr<OsxCTFont> font_;
String text_;
+ String actual_text_;
CFMutableAttributedStringRef cf_attributed_text_;
CTFramesetterRef ct_framesetter_ = nullptr;
@@ -64,6 +72,10 @@ class OsxCTTextLayout : public OsxQuartzResource, public virtual ITextLayout {
int line_count_;
std::vector<CGPoint> line_origins_;
std::vector<CTLineRef> lines_;
+ // The empty line count in the front of the lines.
+ int head_empty_line_count_;
+ // The trailing empty line count in the back of the lines.
+ int tail_empty_line_count_;
Matrix transform_;
};
diff --git a/include/cru/platform/graphics/TextLayout.hpp b/include/cru/platform/graphics/TextLayout.hpp
index 5f98696d..d10c9f22 100644
--- a/include/cru/platform/graphics/TextLayout.hpp
+++ b/include/cru/platform/graphics/TextLayout.hpp
@@ -5,6 +5,8 @@
#include <vector>
namespace cru::platform::graphics {
+// Requirement:
+// All text must be left-top aligned.
struct ITextLayout : virtual IGraphicsResource {
virtual String GetText() = 0;
virtual void SetText(String new_text) = 0;
@@ -15,6 +17,9 @@ struct ITextLayout : virtual IGraphicsResource {
virtual void SetMaxWidth(float max_width) = 0;
virtual void SetMaxHeight(float max_height) = 0;
+ virtual bool IsEditMode() = 0;
+ virtual void SetEditMode(bool enable) = 0;
+
virtual Rect GetTextBounds(bool includingTrailingSpace = false) = 0;
virtual std::vector<Rect> TextRangeRect(const TextRange& text_range) = 0;
// Width is always 0, height is line height.
diff --git a/include/cru/ui/render/TextRenderObject.hpp b/include/cru/ui/render/TextRenderObject.hpp
index 68d2d0ce..d6395f85 100644
--- a/include/cru/ui/render/TextRenderObject.hpp
+++ b/include/cru/ui/render/TextRenderObject.hpp
@@ -42,6 +42,9 @@ class TextRenderObject : public RenderObject {
std::shared_ptr<platform::graphics::IFont> GetFont() const;
void SetFont(std::shared_ptr<platform::graphics::IFont> font);
+ bool IsEditMode();
+ void SetEditMode(bool enable);
+
std::vector<Rect> TextRangeRect(const TextRange& text_range);
Rect TextSinglePoint(gsl::index position, bool trailing);
platform::graphics::TextHitTestResult TextHitTest(const Point& point);
diff --git a/src/osx/graphics/quartz/TextLayout.cpp b/src/osx/graphics/quartz/TextLayout.cpp
index 2cd750d0..e47c0109 100644
--- a/src/osx/graphics/quartz/TextLayout.cpp
+++ b/src/osx/graphics/quartz/TextLayout.cpp
@@ -1,4 +1,5 @@
#include "cru/osx/graphics/quartz/TextLayout.hpp"
+#include "cru/common/StringUtil.hpp"
#include "cru/osx/Convert.hpp"
#include "cru/osx/graphics/quartz/Convert.hpp"
#include "cru/osx/graphics/quartz/Resource.hpp"
@@ -45,11 +46,45 @@ void OsxCTTextLayout::SetFont(std::shared_ptr<IFont> font) {
}
void OsxCTTextLayout::SetText(String new_text) {
+ if (new_text == text_) return;
+
text_ = std::move(new_text);
- CFRelease(cf_attributed_text_);
+ if (text_.empty()) {
+ head_empty_line_count_ = 0;
+ tail_empty_line_count_ = 1;
- CFStringRef s = Convert(text_);
+ actual_text_ = {};
+ } else {
+ head_empty_line_count_ = 0;
+ tail_empty_line_count_ = 0;
+
+ for (auto i = text_.cbegin(); i != text_.cend(); ++i) {
+ if (*i == u'\n') {
+ head_empty_line_count_++;
+ } else {
+ break;
+ }
+ }
+
+ for (auto i = text_.crbegin(); i != text_.crend(); ++i) {
+ if (*i == u'\n') {
+ tail_empty_line_count_++;
+ } else {
+ break;
+ }
+ }
+
+ if (text_.size() == tail_empty_line_count_) {
+ head_empty_line_count_ = 1;
+ actual_text_ = {};
+ } else {
+ actual_text_ = String(text_.cbegin() + head_empty_line_count_,
+ text_.cend() - tail_empty_line_count_);
+ }
+ }
+
+ CFStringRef s = Convert(actual_text_);
cf_attributed_text_ = CFAttributedStringCreateMutable(nullptr, 0);
CFAttributedStringReplaceString(cf_attributed_text_, CFRangeMake(0, 0), s);
Ensures(cf_attributed_text_);
@@ -72,33 +107,91 @@ void OsxCTTextLayout::SetMaxHeight(float max_height) {
RecreateFrame();
}
+bool OsxCTTextLayout::IsEditMode() { return edit_mode_; }
+
+void OsxCTTextLayout::SetEditMode(bool enable) {
+ edit_mode_ = enable;
+ RecreateFrame();
+}
+
Rect OsxCTTextLayout::GetTextBounds(bool includingTrailingSpace) {
- return transform_.TransformRect(DoGetTextBounds(includingTrailingSpace));
+ auto result = DoGetTextBoundsIncludingEmptyLines(includingTrailingSpace);
+ return Rect(0, 0, result.size.width, result.size.height);
}
std::vector<Rect> OsxCTTextLayout::TextRangeRect(const TextRange& text_range) {
- std::vector<Rect> results = DoTextRangeRect(text_range);
+ if (text_.empty()) return {};
+
+ auto tr = text_range;
+ tr.position += head_empty_line_count_;
+ std::vector<CGRect> results = DoTextRangeRect(tr);
+ std::vector<Rect> r;
for (auto& rect : results) {
- rect = transform_.TransformRect(rect);
+ r.push_back(transform_.TransformRect(Convert(rect)));
}
- return results;
+ return r;
}
Rect OsxCTTextLayout::TextSinglePoint(Index position, bool trailing) {
- return transform_.TransformRect(DoTextSinglePoint(position, trailing));
+ Expects(position >= 0 && position <= text_.size());
+
+ if (text_.empty()) return {0, 0, 0, font_->GetFontSize()};
+
+ if (position < head_empty_line_count_) {
+ return {0, position * font_->GetFontSize(), 0, font_->GetFontSize()};
+ } else if (position > text_.size() - tail_empty_line_count_) {
+ return {0,
+ static_cast<float>(DoGetTextBounds().size.height) +
+ (head_empty_line_count_ + position -
+ (text_.size() - tail_empty_line_count_) - 1) *
+ font_->GetFontSize(),
+ 0, font_->GetFontSize()};
+ } else {
+ auto result =
+ DoTextSinglePoint(position - head_empty_line_count_, trailing);
+ return transform_.TransformRect(Convert(result));
+ }
}
TextHitTestResult OsxCTTextLayout::HitTest(const Point& point) {
- auto p = transform_.Inverted()->TransformPoint(point);
+ if (point.y < head_empty_line_count_ * font_->GetFontSize()) {
+ if (point.y < 0) {
+ return {0, false, false};
+ } else {
+ for (int i = 0; i < head_empty_line_count_; ++i) {
+ if (point.y < i * font_->GetFontSize()) {
+ return {i, false, false};
+ }
+ }
+ }
+ }
+
+ auto text_bounds = DoGetTextBounds();
+
+ auto text_height = static_cast<float>(text_bounds.size.height);
+ auto th = text_height + head_empty_line_count_ * font_->GetFontSize();
+ if (point.y >= th) {
+ for (int i = 0; i < tail_empty_line_count_; ++i) {
+ if (point.y < th + i * font_->GetFontSize()) {
+ return {text_.size() - i, false, false};
+ }
+ }
+ }
+
+ auto p = point;
+ p.y -= head_empty_line_count_ * font_->GetFontSize();
+ p.x += text_bounds.origin.x;
+ p.y += text_bounds.origin.y;
+ p.y = text_height - p.y;
for (int i = 0; i < line_count_; i++) {
auto line = lines_[i];
const auto& line_origin = line_origins_[i];
- auto range =
- text_.RangeFromCodePointToCodeUnit(Convert(CTLineGetStringRange(line)));
+ auto range = actual_text_.RangeFromCodePointToCodeUnit(
+ Convert(CTLineGetStringRange(line)));
auto bounds = CTLineGetImageBounds(line, nullptr);
bounds.origin.x += line_origin.x;
@@ -118,13 +211,19 @@ TextHitTestResult OsxCTTextLayout::HitTest(const Point& point) {
pp.y = bounds.origin.y;
if (pp.x < bounds.origin.x) {
- return TextHitTestResult{range.position, false, false};
+ return TextHitTestResult{range.position + head_empty_line_count_, false,
+ false};
} else if (pp.x > bounds.origin.x + bounds.size.width) {
- return TextHitTestResult{range.GetEnd(), false, false};
+ auto po = text_.IndexFromCodePointToCodeUnit(range.GetEnd());
+ if (po != text_.size()) {
+ Utf16PreviousCodePoint(text_, po, &po);
+ }
+ return TextHitTestResult{po + head_empty_line_count_, false, false};
} else {
int position = CTLineGetStringIndexForPosition(
line, CGPointMake(pp.x - line_origin.x, pp.y - line_origin.y));
- return TextHitTestResult{text_.IndexFromCodePointToCodeUnit(position),
+ return TextHitTestResult{text_.IndexFromCodePointToCodeUnit(position) +
+ head_empty_line_count_,
false, true};
}
}
@@ -181,9 +280,11 @@ void OsxCTTextLayout::RecreateFrame() {
auto bounds = DoGetTextBounds(false);
transform_ =
- Matrix::Translation(-bounds.GetRight() / 2, -bounds.GetBottom() / 2) *
+ Matrix::Translation(-bounds.origin.x, -bounds.origin.y) *
+ Matrix::Translation(-bounds.size.width / 2, -bounds.size.height / 2) *
Matrix::Scale(1, -1) *
- Matrix::Translation(bounds.GetRight() / 2, bounds.GetBottom() / 2);
+ Matrix::Translation(bounds.size.width / 2, bounds.size.height / 2) *
+ Matrix::Translation(0, head_empty_line_count_ * font_->GetFontSize());
}
CTFrameRef OsxCTTextLayout::CreateFrameWithColor(const Color& color) {
@@ -214,13 +315,13 @@ String OsxCTTextLayout::GetDebugString() {
max_height_);
}
-Rect OsxCTTextLayout::DoGetTextBounds(bool includingTrailingSpace) {
- if (text_.empty()) return Rect{};
+CGRect OsxCTTextLayout::DoGetTextBounds(bool includingTrailingSpace) {
+ if (actual_text_.empty()) return CGRect{};
float left = std::numeric_limits<float>::max();
- float top = std::numeric_limits<float>::max();
+ float bottom = std::numeric_limits<float>::max();
float right = 0;
- float bottom = 0;
+ float top = 0;
for (int i = 0; i < line_count_; i++) {
auto line = lines_[i];
@@ -236,20 +337,29 @@ Rect OsxCTTextLayout::DoGetTextBounds(bool includingTrailingSpace) {
line_rect.origin.y += line_origin.y;
left = std::min<float>(line_rect.origin.x, left);
- top = std::min<float>(line_rect.origin.y, top);
+ bottom = std::min<float>(line_rect.origin.y, bottom);
right = std::max<float>(line_rect.origin.x + line_rect.size.width, right);
- bottom =
- std::max<float>(line_rect.origin.y + line_rect.size.height, bottom);
+ top = std::max<float>(line_rect.origin.y + line_rect.size.height, top);
}
- return Rect::FromVertices(left, top, right, bottom);
+ return CGRectMake(left, bottom, right - left, top - bottom);
}
-std::vector<Rect> OsxCTTextLayout::DoTextRangeRect(
+CGRect OsxCTTextLayout::DoGetTextBoundsIncludingEmptyLines(
+ bool includingTrailingSpace) {
+ auto result = DoGetTextBounds(includingTrailingSpace);
+
+ result.size.height += head_empty_line_count_ * font_->GetFontSize();
+ result.size.height += tail_empty_line_count_ * font_->GetFontSize();
+
+ return result;
+}
+
+std::vector<CGRect> OsxCTTextLayout::DoTextRangeRect(
const TextRange& text_range) {
const auto r = text_.RangeFromCodeUnitToCodePoint(text_range).Normalize();
- std::vector<Rect> results;
+ std::vector<CGRect> results;
for (int i = 0; i < line_count_; i++) {
auto line = lines_[i];
@@ -268,38 +378,39 @@ std::vector<Rect> OsxCTTextLayout::DoTextRangeRect(
CTLineGetOffsetForStringIndex(line, range.GetEnd(), nullptr);
line_rect.origin.x += start_offset;
line_rect.size.width = end_offset - start_offset;
- results.push_back(Convert(line_rect));
+ results.push_back(line_rect);
}
}
return results;
}
-Rect OsxCTTextLayout::DoTextSinglePoint(Index position, bool trailing) {
- if (lines_.empty()) return Rect{0, 0, 0, font_->GetFontSize()};
+CGRect OsxCTTextLayout::DoTextSinglePoint(Index position, bool trailing) {
+ Expects(position >= 0 && position <= actual_text_.size());
+
+ if (actual_text_.empty()) return CGRectMake(0, 0, 0, font_->GetFontSize());
- position = text_.IndexFromCodeUnitToCodePoint(position);
+ position = actual_text_.IndexFromCodeUnitToCodePoint(position);
for (int i = 0; i < line_count_; i++) {
auto line = lines_[i];
const auto& line_origin = line_origins_[i];
- auto rect = Convert(CTLineGetImageBounds(line, nullptr));
- rect.left += line_origin.x;
- rect.top += line_origin.y;
+ auto rect = CTLineGetImageBounds(line, nullptr);
+ rect.origin.x += line_origin.x;
+ rect.origin.y += line_origin.y;
Range range = Convert(CTLineGetStringRange(line));
if (range.GetStart() <= position && position < range.GetEnd()) {
auto offset = CTLineGetOffsetForStringIndex(line, position, nullptr);
- return Rect(rect.left + offset, rect.top, 0, rect.height);
+ return CGRectMake(rect.origin.x + offset, rect.origin.y, 0,
+ rect.size.height);
}
}
- // TODO: Complete logic here.
-
- auto rect = Convert(CTLineGetImageBounds(lines_.back(), nullptr));
- rect.left = rect.GetRight();
- rect.width = 0;
- return rect;
+ auto rect = CTLineGetImageBounds(lines_.back(), nullptr);
+ rect.origin.x += line_origins_.back().x;
+ rect.origin.y += line_origins_.back().y;
+ return CGRectMake(rect.origin.x + rect.size.width, rect.origin.y, 0,
+ rect.size.height);
}
-
} // namespace cru::platform::graphics::osx::quartz
diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp
index bfc98c06..622401c4 100644
--- a/src/ui/controls/TextBox.cpp
+++ b/src/ui/controls/TextBox.cpp
@@ -20,6 +20,7 @@ TextBox::TextBox()
text_render_object_ = std::make_unique<TextRenderObject>(
theme_resources->text_brush, theme_resources->default_font,
theme_resources->text_selection_brush, theme_resources->caret_brush);
+ text_render_object_->SetEditMode(true);
border_render_object_->AddChild(scroll_render_object_.get(), 0);
scroll_render_object_->AddChild(text_render_object_.get(), 0);
diff --git a/src/ui/render/TextRenderObject.cpp b/src/ui/render/TextRenderObject.cpp
index df95b5f4..e1036072 100644
--- a/src/ui/render/TextRenderObject.cpp
+++ b/src/ui/render/TextRenderObject.cpp
@@ -58,6 +58,12 @@ void TextRenderObject::SetFont(
text_layout_->SetFont(std::move(font));
}
+bool TextRenderObject::IsEditMode() { return text_layout_->IsEditMode(); }
+
+void TextRenderObject::SetEditMode(bool enable) {
+ text_layout_->SetEditMode(enable);
+}
+
std::vector<Rect> TextRenderObject::TextRangeRect(const TextRange& text_range) {
return text_layout_->TextRangeRect(text_range);
}