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 Toolkit::Control& stencil,
59 std::vector<Actor>& clippingDecorationActors,
60 std::vector<Toolkit::TextAnchor>& anchorActors,
61 Text::Controller::UpdateTextType updateTextType)
63 Actor newRenderableActor;
65 if(Text::Controller::NONE_UPDATED != (Text::Controller::MODEL_UPDATED & updateTextType))
69 newRenderableActor = renderer->Render(controller->GetView(),
71 Property::INVALID_INDEX, // Animatable property not supported
76 if(renderableActor != newRenderableActor)
78 UnparentAndReset(backgroundActor);
79 UnparentAndReset(renderableActor);
80 renderableActor = newRenderableActor;
84 backgroundActor = controller->CreateBackgroundActor();
91 const Vector2& scrollOffset = controller->GetTextModel()->GetScrollPosition();
93 float renderableActorPositionX, renderableActorPositionY;
97 renderableActorPositionX = scrollOffset.x + alignmentOffset;
98 renderableActorPositionY = scrollOffset.y;
103 padding = textActor.GetProperty<Extents>(Toolkit::Control::Property::PADDING);
105 // Support Right-To-Left of padding
106 Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(textActor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
107 if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
109 std::swap(padding.start, padding.end);
112 renderableActorPositionX = scrollOffset.x + alignmentOffset + padding.start;
113 renderableActorPositionY = scrollOffset.y + padding.top;
116 renderableActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY));
118 // Make sure the actors are parented correctly with/without clipping
119 Actor self = stencil ? stencil : textActor;
121 Actor highlightActor;
123 for(std::vector<Actor>::iterator it = clippingDecorationActors.begin(),
124 endIt = clippingDecorationActors.end();
131 if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "HighlightActor")
133 highlightActor = *it;
135 else if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "CursorLayerActor")
140 clippingDecorationActors.clear();
142 self.Add(renderableActor);
146 if(decorator && decorator->IsHighlightVisible())
148 self.Add(backgroundActor);
149 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
150 backgroundActor.LowerBelow(highlightActor);
154 renderableActor.Add(backgroundActor);
155 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
156 backgroundActor.LowerToBottom();
159 SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
163 std::size_t TextControlAccessible::GetCharacterCount() const
165 return GetWholeText().size();
168 std::size_t TextControlAccessible::GetCursorOffset() const
173 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
175 if(!ValidateRange(GetWholeText(), startOffset, endOffset))
180 auto rect = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
181 auto extents = GetExtents(type);
189 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
191 // Since DALi supports only one selection, indices other than 0 are ignored
192 if(selectionIndex > 0)
197 auto indices = GetTextController()->GetSelectionIndexes();
198 auto startOffset = static_cast<std::size_t>(indices.first);
199 auto endOffset = static_cast<std::size_t>(indices.second);
200 auto text = GetText(startOffset, endOffset);
202 return {startOffset, endOffset, text};
205 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
207 auto text = GetWholeText();
209 if(!ValidateRange(text, startOffset, endOffset))
216 std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
217 std::string substituteCharacterUtf8;
218 std::string substituteText;
220 Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
222 while(substituteText.length() < endOffset - startOffset)
224 substituteText.append(substituteCharacterUtf8);
227 return substituteText;
230 return text.substr(startOffset, endOffset - startOffset);
233 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
235 Accessibility::Range range{};
239 // Returning empty object, as there is no possibility to parse the textfield
240 // when its content is hidden.
244 auto text = GetWholeText();
245 auto textSize = text.size();
249 case Dali::Accessibility::TextBoundary::CHARACTER:
251 if(offset < textSize)
253 range.content = text[offset];
254 range.startOffset = offset;
255 range.endOffset = offset + 1;
260 case Dali::Accessibility::TextBoundary::WORD:
261 case Dali::Accessibility::TextBoundary::LINE:
263 std::vector<char> breaks(textSize, '\0');
265 if(boundary == Dali::Accessibility::TextBoundary::WORD)
267 Accessibility::Accessible::FindWordSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
271 Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
274 std::size_t index = 0u;
275 std::size_t counter = 0u;
277 while(index < textSize && counter <= offset)
290 if(boundary == Dali::Accessibility::TextBoundary::WORD)
294 if(boundary == Dali::Accessibility::TextBoundary::LINE)
300 if((counter > 0) && ((counter - 1) == offset))
302 range.content = text.substr(start, index - start + 1);
303 range.startOffset = start;
304 range.endOffset = index + 1;
307 if(boundary == Dali::Accessibility::TextBoundary::LINE)
315 case Dali::Accessibility::TextBoundary::SENTENCE: // Not supported by default
316 case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
326 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
328 // Since DALi supports only one selection, indices other than 0 are ignored
329 if(selectionIndex > 0)
334 GetTextController()->SetSelection(0, 0);
339 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
344 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
346 // Since DALi supports only one selection, indices other than 0 are ignored
347 if(selectionIndex > 0)
352 // Lack of ValidateRange() is intentional
354 GetTextController()->SetSelection(startOffset, endOffset);
359 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
361 if(linkIndex < 0 || linkIndex >= GetLinkCount())
366 auto anchor = GetTextAnchors()[linkIndex];
368 return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
371 std::int32_t TextControlAccessible::GetLinkCount() const
373 return static_cast<std::int32_t>(GetTextAnchors().size());
376 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
378 return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
381 std::string TextControlAccessible::GetWholeText() const
385 GetTextController()->GetText(text);
390 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
392 return Toolkit::Text::STAR;
395 bool TextControlAccessible::IsHiddenInput() const
400 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
402 auto size = string.size();
404 if(end <= begin || begin >= size || end > size)
409 // TODO: Check whether the range [begin, end) describes a valid substring:
410 // 1. It does not break multi-byte UTF-8 sequences.
411 // 2. It does not break graphemes (compound emojis, glyphs with combining characters etc.).
416 Accessibility::States EditableTextControlAccessible::CalculateStates()
418 using Dali::Accessibility::State;
420 auto states = DevelControl::ControlAccessible::CalculateStates();
421 auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
423 states[State::EDITABLE] = true;
424 states[State::FOCUSABLE] = true;
425 states[State::FOCUSED] = (Self() == focusControl);
430 std::size_t EditableTextControlAccessible::GetCursorOffset() const
432 return GetTextController()->GetCursorPosition();
435 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
437 if(offset > GetCharacterCount())
442 GetTextController()->ResetCursorPosition(offset);
443 RequestTextRelayout();
448 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
450 auto text = GetWholeText();
452 if(!ValidateRange(text, startPosition, endPosition))
457 GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
462 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
464 if(!CopyText(startPosition, endPosition))
469 return DeleteText(startPosition, endPosition);
472 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
474 auto text = GetWholeText();
476 if(!ValidateRange(text, startPosition, endPosition))
481 return SetTextContents(text.erase(startPosition, endPosition - startPosition));
484 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
486 auto text = GetWholeText();
488 if(!ValidateRange(text, startPosition, startPosition + 1))
493 return SetTextContents(text.insert(startPosition, std::move(newText)));
496 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
498 GetTextController()->SetText(std::move(newContents));
503 } // namespace Dali::Toolkit::Internal