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;
136 clippingDecorationActors.clear();
138 self.Add(renderableActor);
142 if(decorator && decorator->IsHighlightVisible())
144 self.Add(backgroundActor);
145 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
146 backgroundActor.LowerBelow(highlightActor);
150 renderableActor.Add(backgroundActor);
151 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
152 backgroundActor.LowerToBottom();
155 SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
159 std::size_t TextControlAccessible::GetCharacterCount() const
161 return GetWholeText().size();
164 std::size_t TextControlAccessible::GetCursorOffset() const
169 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
171 if(!ValidateRange(GetWholeText(), startOffset, endOffset))
176 auto rect = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
177 auto extents = GetExtents(type);
185 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
187 // Since DALi supports only one selection, indices other than 0 are ignored
188 if(selectionIndex > 0)
193 auto indices = GetTextController()->GetSelectionIndexes();
194 auto startOffset = static_cast<std::size_t>(indices.first);
195 auto endOffset = static_cast<std::size_t>(indices.second);
196 auto text = GetText(startOffset, endOffset);
198 return {startOffset, endOffset, text};
201 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
203 auto text = GetWholeText();
205 if(!ValidateRange(text, startOffset, endOffset))
212 std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
213 std::string substituteCharacterUtf8;
214 std::string substituteText;
216 Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
218 while(substituteText.length() < endOffset - startOffset)
220 substituteText.append(substituteCharacterUtf8);
223 return substituteText;
226 return text.substr(startOffset, endOffset - startOffset);
229 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
231 Accessibility::Range range{};
235 // Returning empty object, as there is no possibility to parse the textfield
236 // when its content is hidden.
240 auto text = GetWholeText();
241 auto textSize = text.size();
245 case Dali::Accessibility::TextBoundary::CHARACTER:
247 if(offset < textSize)
249 range.content = text[offset];
250 range.startOffset = offset;
251 range.endOffset = offset + 1;
256 case Dali::Accessibility::TextBoundary::WORD:
257 case Dali::Accessibility::TextBoundary::LINE:
259 std::vector<char> breaks(textSize, '\0');
261 if(boundary == Dali::Accessibility::TextBoundary::WORD)
263 Accessibility::Accessible::FindWordSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
267 Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
270 std::size_t index = 0u;
271 std::size_t counter = 0u;
273 while(index < textSize && counter <= offset)
286 if(boundary == Dali::Accessibility::TextBoundary::WORD)
290 if(boundary == Dali::Accessibility::TextBoundary::LINE)
296 if((counter > 0) && ((counter - 1) == offset))
298 range.content = text.substr(start, index - start + 1);
299 range.startOffset = start;
300 range.endOffset = index + 1;
303 if(boundary == Dali::Accessibility::TextBoundary::LINE)
311 case Dali::Accessibility::TextBoundary::SENTENCE: // Not supported by default
312 case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
322 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
324 // Since DALi supports only one selection, indices other than 0 are ignored
325 if(selectionIndex > 0)
330 GetTextController()->SetSelection(0, 0);
335 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
340 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
342 // Since DALi supports only one selection, indices other than 0 are ignored
343 if(selectionIndex > 0)
348 // Lack of ValidateRange() is intentional
350 GetTextController()->SetSelection(startOffset, endOffset);
355 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
357 if(linkIndex < 0 || linkIndex >= GetLinkCount())
362 auto anchor = GetTextAnchors()[linkIndex];
364 return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
367 std::int32_t TextControlAccessible::GetLinkCount() const
369 return static_cast<std::int32_t>(GetTextAnchors().size());
372 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
374 return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
377 std::string TextControlAccessible::GetWholeText() const
381 GetTextController()->GetText(text);
386 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
388 return Toolkit::Text::STAR;
391 bool TextControlAccessible::IsHiddenInput() const
396 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
398 auto size = string.size();
400 if(end <= begin || begin >= size || end > size)
405 // TODO: Check whether the range [begin, end) describes a valid substring:
406 // 1. It does not break multi-byte UTF-8 sequences.
407 // 2. It does not break graphemes (compound emojis, glyphs with combining characters etc.).
412 Accessibility::States EditableTextControlAccessible::CalculateStates()
414 using Dali::Accessibility::State;
416 auto states = DevelControl::ControlAccessible::CalculateStates();
417 auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
419 states[State::EDITABLE] = true;
420 states[State::FOCUSABLE] = true;
421 states[State::FOCUSED] = (Self() == focusControl);
426 std::size_t EditableTextControlAccessible::GetCursorOffset() const
428 return GetTextController()->GetCursorPosition();
431 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
433 if(offset > GetCharacterCount())
438 GetTextController()->ResetCursorPosition(offset);
439 RequestTextRelayout();
444 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
446 auto text = GetWholeText();
448 if(!ValidateRange(text, startPosition, endPosition))
453 GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
458 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
460 if(!CopyText(startPosition, endPosition))
465 return DeleteText(startPosition, endPosition);
468 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
470 auto text = GetWholeText();
472 if(!ValidateRange(text, startPosition, endPosition))
477 return SetTextContents(text.erase(startPosition, endPosition - startPosition));
480 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
482 auto text = GetWholeText();
484 if(!ValidateRange(text, startPosition, startPosition + 1))
489 return SetTextContents(text.insert(startPosition, std::move(newText)));
492 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
494 GetTextController()->SetText(std::move(newContents));
499 } // namespace Dali::Toolkit::Internal