2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/public-api/actors/layer.h>
21 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
22 #include <dali-toolkit/devel-api/controls/control-devel.h>
23 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
24 #include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
25 #include <dali-toolkit/internal/text/character-set-conversion.h>
26 #include <dali-toolkit/internal/text/hidden-text.h>
27 #include <dali-toolkit/internal/text/text-view.h>
29 namespace Dali::Toolkit::Internal
31 void CommonTextUtils::SynchronizeTextAnchorsInParent(
33 Text::ControllerPtr controller,
34 std::vector<Toolkit::TextAnchor>& anchorActors)
36 for(auto& anchorActor : anchorActors)
38 parent.Remove(anchorActor);
40 if(Dali::Accessibility::IsUp())
42 controller->GetAnchorActors(anchorActors);
43 for(auto& anchorActor : anchorActors)
45 parent.Add(anchorActor);
50 void CommonTextUtils::RenderText(
52 Text::RendererPtr renderer,
53 Text::ControllerPtr controller,
54 Text::DecoratorPtr decorator,
55 float& alignmentOffset,
56 Actor& renderableActor,
57 Actor& backgroundActor,
58 Actor& cursorLayerActor,
59 Toolkit::Control& stencil,
60 std::vector<Actor>& clippingDecorationActors,
61 std::vector<Toolkit::TextAnchor>& anchorActors,
62 Text::Controller::UpdateTextType updateTextType)
64 Actor newRenderableActor;
66 if(Text::Controller::NONE_UPDATED != (Text::Controller::MODEL_UPDATED & updateTextType))
70 newRenderableActor = renderer->Render(controller->GetView(),
72 Property::INVALID_INDEX, // Animatable property not supported
77 if(renderableActor != newRenderableActor)
79 UnparentAndReset(backgroundActor);
80 UnparentAndReset(renderableActor);
81 renderableActor = newRenderableActor;
85 backgroundActor = controller->CreateBackgroundActor();
92 const Vector2& scrollOffset = controller->GetTextModel()->GetScrollPosition();
94 float renderableActorPositionX, renderableActorPositionY;
98 renderableActorPositionX = scrollOffset.x + alignmentOffset;
99 renderableActorPositionY = scrollOffset.y;
104 padding = textActor.GetProperty<Extents>(Toolkit::Control::Property::PADDING);
106 // Support Right-To-Left of padding
107 Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(textActor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
108 if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
110 std::swap(padding.start, padding.end);
113 renderableActorPositionX = scrollOffset.x + alignmentOffset + padding.start;
114 renderableActorPositionY = scrollOffset.y + padding.top;
117 renderableActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY));
119 // Make sure the actors are parented correctly with/without clipping
120 Actor self = stencil ? stencil : textActor;
122 Actor highlightActor;
124 for(std::vector<Actor>::iterator it = clippingDecorationActors.begin(),
125 endIt = clippingDecorationActors.end();
132 if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "HighlightActor")
134 highlightActor = *it;
137 clippingDecorationActors.clear();
139 self.Add(renderableActor);
143 if(decorator && decorator->IsHighlightVisible())
145 self.Add(backgroundActor);
146 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
147 backgroundActor.LowerBelow(highlightActor);
151 renderableActor.Add(backgroundActor);
152 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
153 backgroundActor.LowerToBottom();
159 cursorLayerActor.RaiseToTop();
162 SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
166 std::size_t TextControlAccessible::GetCharacterCount() const
168 return GetWholeText().size();
171 std::size_t TextControlAccessible::GetCursorOffset() const
176 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
178 if(!ValidateRange(GetWholeText(), startOffset, endOffset))
183 auto rect = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
184 auto extents = GetExtents(type);
192 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
194 // Since DALi supports only one selection, indices other than 0 are ignored
195 if(selectionIndex > 0)
200 auto indices = GetTextController()->GetSelectionIndexes();
201 auto startOffset = static_cast<std::size_t>(indices.first);
202 auto endOffset = static_cast<std::size_t>(indices.second);
203 auto text = GetText(startOffset, endOffset);
205 return {startOffset, endOffset, text};
208 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
210 auto text = GetWholeText();
212 if(!ValidateRange(text, startOffset, endOffset))
219 std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
220 std::string substituteCharacterUtf8;
221 std::string substituteText;
223 Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
225 while(substituteText.length() < endOffset - startOffset)
227 substituteText.append(substituteCharacterUtf8);
230 return substituteText;
233 return text.substr(startOffset, endOffset - startOffset);
236 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
238 Accessibility::Range range{};
242 // Returning empty object, as there is no possibility to parse the textfield
243 // when its content is hidden.
247 auto text = GetWholeText();
248 auto textSize = text.size();
252 case Dali::Accessibility::TextBoundary::CHARACTER:
254 if(offset < textSize)
256 range.content = text[offset];
257 range.startOffset = offset;
258 range.endOffset = offset + 1;
263 case Dali::Accessibility::TextBoundary::WORD:
264 case Dali::Accessibility::TextBoundary::LINE:
266 std::vector<char> breaks(textSize, '\0');
268 if(boundary == Dali::Accessibility::TextBoundary::WORD)
270 Accessibility::Accessible::FindWordSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
274 Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
277 std::size_t index = 0u;
278 std::size_t counter = 0u;
280 while(index < textSize && counter <= offset)
293 if(boundary == Dali::Accessibility::TextBoundary::WORD)
297 if(boundary == Dali::Accessibility::TextBoundary::LINE)
303 if((counter > 0) && ((counter - 1) == offset))
305 range.content = text.substr(start, index - start + 1);
306 range.startOffset = start;
307 range.endOffset = index + 1;
310 if(boundary == Dali::Accessibility::TextBoundary::LINE)
318 case Dali::Accessibility::TextBoundary::SENTENCE: // Not supported by default
319 case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
329 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
331 // Since DALi supports only one selection, indices other than 0 are ignored
332 if(selectionIndex > 0)
337 GetTextController()->SetSelection(0, 0);
342 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
347 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
349 // Since DALi supports only one selection, indices other than 0 are ignored
350 if(selectionIndex > 0)
355 // Lack of ValidateRange() is intentional
357 GetTextController()->SetSelection(startOffset, endOffset);
362 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
364 if(linkIndex < 0 || linkIndex >= GetLinkCount())
369 auto anchor = GetTextAnchors()[linkIndex];
371 return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
374 std::int32_t TextControlAccessible::GetLinkCount() const
376 return static_cast<std::int32_t>(GetTextAnchors().size());
379 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
381 return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
384 std::string TextControlAccessible::GetWholeText() const
388 GetTextController()->GetText(text);
393 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
395 return Toolkit::Text::STAR;
398 bool TextControlAccessible::IsHiddenInput() const
403 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
405 auto size = string.size();
407 if(end <= begin || begin >= size || end > size)
412 // TODO: Check whether the range [begin, end) describes a valid substring:
413 // 1. It does not break multi-byte UTF-8 sequences.
414 // 2. It does not break graphemes (compound emojis, glyphs with combining characters etc.).
419 Accessibility::States EditableTextControlAccessible::CalculateStates()
421 using Dali::Accessibility::State;
423 auto states = DevelControl::ControlAccessible::CalculateStates();
424 auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
426 states[State::EDITABLE] = true;
427 states[State::FOCUSABLE] = true;
428 states[State::FOCUSED] = (Self() == focusControl);
433 std::size_t EditableTextControlAccessible::GetCursorOffset() const
435 return GetTextController()->GetCursorPosition();
438 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
440 if(offset > GetCharacterCount())
445 GetTextController()->ResetCursorPosition(offset);
446 RequestTextRelayout();
451 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
453 auto text = GetWholeText();
455 if(!ValidateRange(text, startPosition, endPosition))
460 GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
465 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
467 if(!CopyText(startPosition, endPosition))
472 return DeleteText(startPosition, endPosition);
475 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
477 auto text = GetWholeText();
479 if(!ValidateRange(text, startPosition, endPosition))
484 return SetTextContents(text.erase(startPosition, endPosition - startPosition));
487 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
489 auto text = GetWholeText();
491 if(!ValidateRange(text, startPosition, startPosition + 1))
496 return SetTextContents(text.insert(startPosition, std::move(newText)));
499 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
501 GetTextController()->SetText(std::move(newContents));
506 } // namespace Dali::Toolkit::Internal