[dali-toolkit] fix set max character length when text already set
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-text-updater.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/text-controller-text-updater.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <memory.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/character-set-conversion.h>
27 #include <dali-toolkit/internal/text/markup-processor.h>
28 #include <dali-toolkit/internal/text/text-controller-impl.h>
29 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
30
31 namespace
32 {
33 #if defined(DEBUG_ENABLED)
34 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
35 #endif
36
37 } // namespace
38
39 namespace Dali
40 {
41 namespace Toolkit
42 {
43 namespace Text
44 {
45 void Controller::TextUpdater::SetText(Controller& controller, const std::string& text)
46 {
47   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText\n");
48
49   Controller::Impl& impl = *controller.mImpl;
50
51   // Reset keyboard as text changed
52   impl.ResetInputMethodContext();
53
54   // Remove the previously set text and style.
55   ResetText(controller);
56
57   // Remove the style.
58   controller.ClearStyleData();
59
60   CharacterIndex lastCursorIndex = 0u;
61
62   EventData*& eventData = impl.mEventData;
63
64   if(nullptr != eventData)
65   {
66     // If popup shown then hide it by switching to Editing state
67     if((EventData::SELECTING == eventData->mState) ||
68        (EventData::EDITING_WITH_POPUP == eventData->mState) ||
69        (EventData::EDITING_WITH_GRAB_HANDLE == eventData->mState) ||
70        (EventData::EDITING_WITH_PASTE_POPUP == eventData->mState))
71     {
72       impl.ChangeState(EventData::EDITING);
73     }
74   }
75
76   if(!text.empty())
77   {
78     ModelPtr&        model        = impl.mModel;
79     LogicalModelPtr& logicalModel = model->mLogicalModel;
80     model->mVisualModel->SetTextColor(impl.mTextColor);
81
82     MarkupProcessData markupProcessData(logicalModel->mColorRuns,
83                                         logicalModel->mFontDescriptionRuns,
84                                         logicalModel->mEmbeddedItems);
85
86     Length         textSize = 0u;
87     const uint8_t* utf8     = NULL;
88     if(impl.mMarkupProcessorEnabled)
89     {
90       ProcessMarkupString(text, markupProcessData);
91       textSize = markupProcessData.markupProcessedText.size();
92
93       // This is a bit horrible but std::string returns a (signed) char*
94       utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
95     }
96     else
97     {
98       textSize = text.size();
99
100       // This is a bit horrible but std::string returns a (signed) char*
101       utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
102     }
103
104     //  Convert text into UTF-32
105     Vector<Character>& utf32Characters = logicalModel->mText;
106     utf32Characters.Resize(textSize);
107
108     // Transform a text array encoded in utf8 into an array encoded in utf32.
109     // It returns the actual number of characters.
110     Length characterCount = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
111     utf32Characters.Resize(characterCount);
112
113     DALI_ASSERT_DEBUG(textSize >= characterCount && "Invalid UTF32 conversion length");
114     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SetText %p UTF8 size %d, UTF32 size %d\n", &controller, textSize, logicalModel->mText.Count());
115
116     // The characters to be added.
117     impl.mTextUpdateInfo.mNumberOfCharactersToAdd = logicalModel->mText.Count();
118
119     // To reset the cursor position
120     lastCursorIndex = characterCount;
121
122     // Update the rest of the model during size negotiation
123     impl.QueueModifyEvent(ModifyEvent::TEXT_REPLACED);
124
125     // The natural size needs to be re-calculated.
126     impl.mRecalculateNaturalSize = true;
127
128     // The text direction needs to be updated.
129     impl.mUpdateTextDirection = true;
130
131     // Apply modifications to the model
132     impl.mOperationsPending = ALL_OPERATIONS;
133   }
134   else
135   {
136     controller.ShowPlaceholderText();
137   }
138
139   // Resets the cursor position.
140   controller.ResetCursorPosition(lastCursorIndex);
141
142   // Scrolls the text to make the cursor visible.
143   controller.ResetScrollPosition();
144
145   impl.RequestRelayout();
146
147   if(nullptr != eventData)
148   {
149     // Cancel previously queued events
150     eventData->mEventQueue.clear();
151   }
152
153   // Do this last since it provides callbacks into application code.
154   if(NULL != impl.mEditableControlInterface)
155   {
156     impl.mEditableControlInterface->TextChanged(true);
157   }
158 }
159
160 void Controller::TextUpdater::InsertText(Controller& controller, const std::string& text, Controller::InsertType type)
161 {
162   Controller::Impl& impl      = *controller.mImpl;
163   EventData*&       eventData = impl.mEventData;
164
165   DALI_ASSERT_DEBUG(nullptr != eventData && "Unexpected InsertText")
166
167   if(NULL == eventData)
168   {
169     return;
170   }
171
172   bool removedPrevious  = false;
173   bool removedSelected  = false;
174   bool maxLengthReached = false;
175
176   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::InsertText %p %s (%s) mPrimaryCursorPosition %d mPreEditFlag %d mPreEditStartPosition %d mPreEditLength %d\n", &controller, text.c_str(), (COMMIT == type ? "COMMIT" : "PRE_EDIT"), eventData->mPrimaryCursorPosition, eventData->mPreEditFlag, eventData->mPreEditStartPosition, eventData->mPreEditLength);
177
178   ModelPtr&        model        = impl.mModel;
179   LogicalModelPtr& logicalModel = model->mLogicalModel;
180
181   // TODO: At the moment the underline runs are only for pre-edit.
182   model->mVisualModel->mUnderlineRuns.Clear();
183
184   // Remove the previous InputMethodContext pre-edit.
185   if(eventData->mPreEditFlag && (0u != eventData->mPreEditLength))
186   {
187     removedPrevious = RemoveText(controller,
188                                  -static_cast<int>(eventData->mPrimaryCursorPosition - eventData->mPreEditStartPosition),
189                                  eventData->mPreEditLength,
190                                  DONT_UPDATE_INPUT_STYLE);
191
192     eventData->mPrimaryCursorPosition = eventData->mPreEditStartPosition;
193     eventData->mPreEditLength         = 0u;
194   }
195   else
196   {
197     // Remove the previous Selection.
198     removedSelected = RemoveSelectedText(controller);
199   }
200
201   Vector<Character> utf32Characters;
202   Length            characterCount = 0u;
203
204   if(!text.empty())
205   {
206     //  Convert text into UTF-32
207     utf32Characters.Resize(text.size());
208
209     // This is a bit horrible but std::string returns a (signed) char*
210     const uint8_t* utf8 = reinterpret_cast<const uint8_t*>(text.c_str());
211
212     // Transform a text array encoded in utf8 into an array encoded in utf32.
213     // It returns the actual number of characters.
214     characterCount = Utf8ToUtf32(utf8, text.size(), utf32Characters.Begin());
215     utf32Characters.Resize(characterCount);
216
217     DALI_ASSERT_DEBUG(text.size() >= utf32Characters.Count() && "Invalid UTF32 conversion length");
218     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "UTF8 size %d, UTF32 size %d\n", text.size(), utf32Characters.Count());
219   }
220
221   if(0u != utf32Characters.Count()) // Check if Utf8ToUtf32 conversion succeeded
222   {
223     // The placeholder text is no longer needed
224     if(impl.IsShowingPlaceholderText())
225     {
226       ResetText(controller);
227     }
228
229     impl.ChangeState(EventData::EDITING);
230
231     // Handle the InputMethodContext (predicitive text) state changes
232     if(COMMIT == type)
233     {
234       // InputMethodContext is no longer handling key-events
235       impl.ClearPreEditFlag();
236     }
237     else // PRE_EDIT
238     {
239       if(!eventData->mPreEditFlag)
240       {
241         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Entered PreEdit state\n");
242
243         // Record the start of the pre-edit text
244         eventData->mPreEditStartPosition = eventData->mPrimaryCursorPosition;
245       }
246
247       eventData->mPreEditLength = utf32Characters.Count();
248       eventData->mPreEditFlag   = true;
249
250       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "mPreEditStartPosition %d mPreEditLength %d\n", eventData->mPreEditStartPosition, eventData->mPreEditLength);
251     }
252
253     const Length numberOfCharactersInModel = logicalModel->mText.Count();
254
255     // Restrict new text to fit within Maximum characters setting.
256     Length temp_length      = (impl.mMaximumNumberOfCharacters > numberOfCharactersInModel ? impl.mMaximumNumberOfCharacters - numberOfCharactersInModel : 0);
257     Length maxSizeOfNewText = std::min(temp_length, characterCount);
258     maxLengthReached        = (characterCount > maxSizeOfNewText);
259
260     // The cursor position.
261     CharacterIndex& cursorIndex = eventData->mPrimaryCursorPosition;
262
263     // Update the text's style.
264
265     // Updates the text style runs by adding characters.
266     logicalModel->UpdateTextStyleRuns(cursorIndex, maxSizeOfNewText);
267
268     // Get the character index from the cursor index.
269     const CharacterIndex styleIndex = (cursorIndex > 0u) ? cursorIndex - 1u : 0u;
270
271     // Retrieve the text's style for the given index.
272     InputStyle style;
273     impl.RetrieveDefaultInputStyle(style);
274     logicalModel->RetrieveStyle(styleIndex, style);
275
276     InputStyle& inputStyle = eventData->mInputStyle;
277
278     // Whether to add a new text color run.
279     const bool addColorRun = (style.textColor != inputStyle.textColor) && !inputStyle.isDefaultColor;
280
281     // Whether to add a new font run.
282     const bool addFontNameRun   = (style.familyName != inputStyle.familyName) && inputStyle.isFamilyDefined;
283     const bool addFontWeightRun = (style.weight != inputStyle.weight) && inputStyle.isWeightDefined;
284     const bool addFontWidthRun  = (style.width != inputStyle.width) && inputStyle.isWidthDefined;
285     const bool addFontSlantRun  = (style.slant != inputStyle.slant) && inputStyle.isSlantDefined;
286     const bool addFontSizeRun   = (style.size != inputStyle.size) && inputStyle.isSizeDefined;
287
288     // Add style runs.
289     if(addColorRun)
290     {
291       const VectorBase::SizeType numberOfRuns = logicalModel->mColorRuns.Count();
292       logicalModel->mColorRuns.Resize(numberOfRuns + 1u);
293
294       ColorRun& colorRun                       = *(logicalModel->mColorRuns.Begin() + numberOfRuns);
295       colorRun.color                           = inputStyle.textColor;
296       colorRun.characterRun.characterIndex     = cursorIndex;
297       colorRun.characterRun.numberOfCharacters = maxSizeOfNewText;
298     }
299
300     if(addFontNameRun ||
301        addFontWeightRun ||
302        addFontWidthRun ||
303        addFontSlantRun ||
304        addFontSizeRun)
305     {
306       const VectorBase::SizeType numberOfRuns = logicalModel->mFontDescriptionRuns.Count();
307       logicalModel->mFontDescriptionRuns.Resize(numberOfRuns + 1u);
308
309       FontDescriptionRun& fontDescriptionRun = *(logicalModel->mFontDescriptionRuns.Begin() + numberOfRuns);
310
311       if(addFontNameRun)
312       {
313         fontDescriptionRun.familyLength = inputStyle.familyName.size();
314         fontDescriptionRun.familyName   = new char[fontDescriptionRun.familyLength];
315         memcpy(fontDescriptionRun.familyName, inputStyle.familyName.c_str(), fontDescriptionRun.familyLength);
316         fontDescriptionRun.familyDefined = true;
317
318         // The memory allocated for the font family name is freed when the font description is removed from the logical model.
319       }
320
321       if(addFontWeightRun)
322       {
323         fontDescriptionRun.weight        = inputStyle.weight;
324         fontDescriptionRun.weightDefined = true;
325       }
326
327       if(addFontWidthRun)
328       {
329         fontDescriptionRun.width        = inputStyle.width;
330         fontDescriptionRun.widthDefined = true;
331       }
332
333       if(addFontSlantRun)
334       {
335         fontDescriptionRun.slant        = inputStyle.slant;
336         fontDescriptionRun.slantDefined = true;
337       }
338
339       if(addFontSizeRun)
340       {
341         fontDescriptionRun.size        = static_cast<PointSize26Dot6>(inputStyle.size * impl.mFontSizeScale * 64.f);
342         fontDescriptionRun.sizeDefined = true;
343       }
344
345       fontDescriptionRun.characterRun.characterIndex     = cursorIndex;
346       fontDescriptionRun.characterRun.numberOfCharacters = maxSizeOfNewText;
347     }
348
349     // Insert at current cursor position.
350     Vector<Character>& modifyText = logicalModel->mText;
351
352     auto pos = modifyText.End();
353     if(cursorIndex < numberOfCharactersInModel)
354     {
355       pos = modifyText.Begin() + cursorIndex;
356     }
357     unsigned int realPos = pos - modifyText.Begin();
358     modifyText.Insert(pos, utf32Characters.Begin(), utf32Characters.Begin() + maxSizeOfNewText);
359
360     if(NULL != impl.mEditableControlInterface)
361     {
362       impl.mEditableControlInterface->TextInserted(realPos, maxSizeOfNewText, text);
363     }
364
365     TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
366
367     // Mark the first paragraph to be updated.
368     if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
369     {
370       textUpdateInfo.mCharacterIndex             = 0;
371       textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
372       textUpdateInfo.mNumberOfCharactersToAdd    = numberOfCharactersInModel + maxSizeOfNewText;
373       textUpdateInfo.mClearAll                   = true;
374     }
375     else
376     {
377       textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
378       textUpdateInfo.mNumberOfCharactersToAdd += maxSizeOfNewText;
379     }
380
381     // Update the cursor index.
382     cursorIndex += maxSizeOfNewText;
383
384     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition);
385   }
386
387   if((0u == logicalModel->mText.Count()) &&
388      impl.IsPlaceholderAvailable())
389   {
390     // Show place-holder if empty after removing the pre-edit text
391     controller.ShowPlaceholderText();
392     eventData->mUpdateCursorPosition = true;
393     impl.ClearPreEditFlag();
394   }
395   else if(removedPrevious ||
396           removedSelected ||
397           (0 != utf32Characters.Count()))
398   {
399     // Queue an inserted event
400     impl.QueueModifyEvent(ModifyEvent::TEXT_INSERTED);
401
402     eventData->mUpdateCursorPosition = true;
403     if(removedSelected)
404     {
405       eventData->mScrollAfterDelete = true;
406     }
407     else
408     {
409       eventData->mScrollAfterUpdatePosition = true;
410     }
411   }
412
413   if(maxLengthReached)
414   {
415     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "MaxLengthReached (%d)\n", logicalModel->mText.Count());
416
417     impl.ResetInputMethodContext();
418
419     if(NULL != impl.mEditableControlInterface)
420     {
421       // Do this last since it provides callbacks into application code
422       impl.mEditableControlInterface->MaxLengthReached();
423     }
424   }
425 }
426
427 void Controller::TextUpdater::PasteText(Controller& controller, const std::string& stringToPaste)
428 {
429   InsertText(controller, stringToPaste, Text::Controller::COMMIT);
430   Controller::Impl& impl = *controller.mImpl;
431   impl.ChangeState(EventData::EDITING);
432   impl.RequestRelayout();
433
434   if(NULL != impl.mEditableControlInterface)
435   {
436     // Do this last since it provides callbacks into application code
437     impl.mEditableControlInterface->TextChanged(true);
438   }
439 }
440
441 bool Controller::TextUpdater::RemoveText(
442   Controller&          controller,
443   int                  cursorOffset,
444   int                  numberOfCharacters,
445   UpdateInputStyleType type)
446 {
447   bool removed = false;
448
449   Controller::Impl& impl      = *controller.mImpl;
450   EventData*&       eventData = impl.mEventData;
451
452   if(nullptr == eventData)
453   {
454     return removed;
455   }
456
457   ModelPtr&        model        = impl.mModel;
458   LogicalModelPtr& logicalModel = model->mLogicalModel;
459
460   DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p mText.Count() %d cursor %d cursorOffset %d numberOfCharacters %d\n", &controller, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition, cursorOffset, numberOfCharacters);
461
462   if(!impl.IsShowingPlaceholderText())
463   {
464     // Delete at current cursor position
465     Vector<Character>& currentText    = logicalModel->mText;
466     CharacterIndex&    oldCursorIndex = eventData->mPrimaryCursorPosition;
467
468     CharacterIndex cursorIndex = 0;
469
470     // Validate the cursor position & number of characters
471     if((static_cast<int>(eventData->mPrimaryCursorPosition) + cursorOffset) >= 0)
472     {
473       cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset;
474     }
475
476     if((cursorIndex + numberOfCharacters) > currentText.Count())
477     {
478       numberOfCharacters = currentText.Count() - cursorIndex;
479     }
480
481     TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
482
483     if(eventData->mPreEditFlag || // If the preedit flag is enabled, it means two (or more) of them came together i.e. when two keys have been pressed at the same time.
484        ((cursorIndex + numberOfCharacters) <= textUpdateInfo.mPreviousNumberOfCharacters))
485     {
486       // Mark the paragraphs to be updated.
487       if(Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout())
488       {
489         textUpdateInfo.mCharacterIndex             = 0;
490         textUpdateInfo.mNumberOfCharactersToRemove = textUpdateInfo.mPreviousNumberOfCharacters;
491         textUpdateInfo.mNumberOfCharactersToAdd    = textUpdateInfo.mPreviousNumberOfCharacters - numberOfCharacters;
492         textUpdateInfo.mClearAll                   = true;
493       }
494       else
495       {
496         textUpdateInfo.mCharacterIndex = std::min(cursorIndex, textUpdateInfo.mCharacterIndex);
497         textUpdateInfo.mNumberOfCharactersToRemove += numberOfCharacters;
498       }
499
500       // Update the input style and remove the text's style before removing the text.
501
502       if(UPDATE_INPUT_STYLE == type)
503       {
504         InputStyle& eventDataInputStyle = eventData->mInputStyle;
505
506         // Keep a copy of the current input style.
507         InputStyle currentInputStyle;
508         currentInputStyle.Copy(eventDataInputStyle);
509
510         // Set first the default input style.
511         impl.RetrieveDefaultInputStyle(eventDataInputStyle);
512
513         // Update the input style.
514         logicalModel->RetrieveStyle(cursorIndex, eventDataInputStyle);
515
516         // Compare if the input style has changed.
517         const bool hasInputStyleChanged = !currentInputStyle.Equal(eventDataInputStyle);
518
519         if(hasInputStyleChanged)
520         {
521           const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventDataInputStyle);
522           // Queue the input style changed signal.
523           eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
524         }
525       }
526
527       // If the number of current text and the number of characters to be deleted are same,
528       // it means all texts should be removed and all Preedit variables should be initialized.
529       if((currentText.Count() - numberOfCharacters == 0) && (cursorIndex == 0))
530       {
531         impl.ClearPreEditFlag();
532         textUpdateInfo.mNumberOfCharactersToAdd = 0;
533       }
534
535       // Updates the text style runs by removing characters. Runs with no characters are removed.
536       logicalModel->UpdateTextStyleRuns(cursorIndex, -numberOfCharacters);
537
538       // Remove the characters.
539       Vector<Character>::Iterator first = currentText.Begin() + cursorIndex;
540       Vector<Character>::Iterator last  = first + numberOfCharacters;
541
542       if(NULL != impl.mEditableControlInterface)
543       {
544         std::string utf8;
545         Utf32ToUtf8(first, numberOfCharacters, utf8);
546         impl.mEditableControlInterface->TextDeleted(cursorIndex, numberOfCharacters, utf8);
547       }
548
549       currentText.Erase(first, last);
550
551       // Cursor position retreat
552       oldCursorIndex = cursorIndex;
553
554       eventData->mScrollAfterDelete = true;
555
556       if(EventData::INACTIVE == eventData->mState)
557       {
558         impl.ChangeState(EventData::EDITING);
559       }
560
561       DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p removed %d\n", &controller, numberOfCharacters);
562       removed = true;
563     }
564   }
565
566   return removed;
567 }
568
569 bool Controller::TextUpdater::RemoveSelectedText(Controller& controller)
570 {
571   bool textRemoved(false);
572
573   Controller::Impl& impl = *controller.mImpl;
574
575   if(EventData::SELECTING == impl.mEventData->mState)
576   {
577     std::string removedString;
578     impl.RetrieveSelection(removedString, true);
579
580     if(!removedString.empty())
581     {
582       textRemoved = true;
583       impl.ChangeState(EventData::EDITING);
584     }
585   }
586
587   return textRemoved;
588 }
589
590 void Controller::TextUpdater::ResetText(Controller& controller)
591 {
592   Controller::Impl& impl         = *controller.mImpl;
593   LogicalModelPtr&  logicalModel = impl.mModel->mLogicalModel;
594
595   // Reset buffers.
596   logicalModel->mText.Clear();
597
598   // Reset the embedded images buffer.
599   logicalModel->ClearEmbeddedImages();
600
601   // We have cleared everything including the placeholder-text
602   impl.PlaceholderCleared();
603
604   impl.mTextUpdateInfo.mCharacterIndex             = 0u;
605   impl.mTextUpdateInfo.mNumberOfCharactersToRemove = impl.mTextUpdateInfo.mPreviousNumberOfCharacters;
606   impl.mTextUpdateInfo.mNumberOfCharactersToAdd    = 0u;
607
608   // Clear any previous text.
609   impl.mTextUpdateInfo.mClearAll = true;
610
611   // The natural size needs to be re-calculated.
612   impl.mRecalculateNaturalSize = true;
613
614   // The text direction needs to be updated.
615   impl.mUpdateTextDirection = true;
616
617   // Apply modifications to the model
618   impl.mOperationsPending = ALL_OPERATIONS;
619 }
620
621 } // namespace Text
622
623 } // namespace Toolkit
624
625 } // namespace Dali