./execute.sh dali-toolkit
-To get coverage output (you need to first build dali libraries with
+To get full coverage output (you need to first build dali libraries with
--coverage), run
./coverage.sh
+To check the coverage of your patch, (the build server uses its own copy
+of these scripts), you can use
+
+ ./patch-coverage.pl -q [diff-spec]
+
+to get a summary, or
+
+ ./patch-coverage.pl [diff-spec]
+
+to get textual output, or
+
+ ./patch-coverage.pl -o out.html [diff-spec]
+
+to get HTML output (used by build server).
+
+diff-spec is any refspec accepted by git-diff. If it's left out, it creates
+a refspec to the latest commit, or uses the index/working tree.
+
Testing on target
=================
-To build for target, first build and install dali-core, dali-adaptor and dali-toolkit, then build dali-capi without --keep-packs option.
+To build for target, first build and install dali-core, dali-adaptor and dali-toolkit.
You will need to install libconfig-tiny-perl:
use Git;
use Getopt::Long;
use Error qw(:try);
-use HTML::Element;
use Pod::Usage;
use File::Basename;
-#use Data::Dumper;
use File::stat;
use Scalar::Util qw /looks_like_number/;
use Cwd qw /getcwd/;
use Term::ANSIColor qw(:constants);
+use Data::Dumper;
-# Program to run gcov on files in patch (that are in source dirs - needs to be dali-aware).
+# Dali specific program to run lcov and parse output for files in patch
-# A) Get patch
-# B) Remove uninteresting files
-# C) Find matching gcno/gcda files
-# D) Copy and rename them to match source prefix (i.e. strip library name off front)
-# E) Generate patch output with covered/uncovered lines marked in green/red
-# F) Generate coverage data for changed lines
-# G) Exit status should be 0 for high coverage (90% line coverage for all new/changed lines)
+# A) Generate lcov output from lib & test cases
+# B) Get patch using git diff
+# C) Generate patch output with covered/uncovered lines marked in green/red
+# D) Generate coverage data for changed lines
+# E) Exit status should be 0 for high coverage (90% line coverage for all new/changed lines)
# or 1 for low coverage
# Sources for conversion of gcno/gcda files:
-# ~/bin/lcov
+# /usr/bin/lcov & genhtml
# Python git-coverage (From http://stef.thewalter.net/git-coverage-useful-code-coverage.html)
+# From genhtml:
+sub read_info_file($);
+sub get_info_entry($);
+sub set_info_entry($$$$$$$$$;$$$$$$);
+sub combine_info_entries($$$);
+sub combine_info_files($$);
+sub compress_brcount($);
+sub brcount_to_db($);
+sub db_to_brcount($;$);
+sub brcount_db_combine($$$);
+sub add_counts($$);
+sub info(@);
+
our $repo = Git->repository();
our $debug=0;
our $pd_debug=0;
+our $root;
+our %info_data; # Hash containing all data from .info files
+
+# Settings from genhtml:
+our $func_coverage; # If set, generate function coverage statistics
+our $no_func_coverage; # Disable func_coverage
+our $br_coverage; # If set, generate branch coverage statistics
+our $no_br_coverage; # Disable br_coverage
+our $sort = 1; # If set, provide directory listings with sorted entries
+our $no_sort; # Disable sort
+
+# Branch data combination types
+our $BR_SUB = 0;
+our $BR_ADD = 1;
+
+# Command line options
our $opt_cached;
our $opt_help;
our $opt_output;
pod2usage(1) if $opt_help;
+# From genhtml:
+#
+# read_info_file(info_filename)
+#
+# Read in the contents of the .info file specified by INFO_FILENAME. Data will
+# be returned as a reference to a hash containing the following mappings:
+#
+# %result: for each filename found in file -> \%data
+#
+# %data: "test" -> \%testdata
+# "sum" -> \%sumcount
+# "func" -> \%funcdata
+# "found" -> $lines_found (number of instrumented lines found in file)
+# "hit" -> $lines_hit (number of executed lines in file)
+# "f_found" -> $fn_found (number of instrumented functions found in file)
+# "f_hit" -> $fn_hit (number of executed functions in file)
+# "b_found" -> $br_found (number of instrumented branches found in file)
+# "b_hit" -> $br_hit (number of executed branches in file)
+# "check" -> \%checkdata
+# "testfnc" -> \%testfncdata
+# "sumfnc" -> \%sumfnccount
+# "testbr" -> \%testbrdata
+# "sumbr" -> \%sumbrcount
+#
+# %testdata : name of test affecting this file -> \%testcount
+# %testfncdata: name of test affecting this file -> \%testfnccount
+# %testbrdata: name of test affecting this file -> \%testbrcount
+#
+# %testcount : line number -> execution count for a single test
+# %testfnccount: function name -> execution count for a single test
+# %testbrcount : line number -> branch coverage data for a single test
+# %sumcount : line number -> execution count for all tests
+# %sumfnccount : function name -> execution count for all tests
+# %sumbrcount : line number -> branch coverage data for all tests
+# %funcdata : function name -> line number
+# %checkdata : line number -> checksum of source code line
+# $brdata : vector of items: block, branch, taken
+#
+# Note that .info file sections referring to the same file and test name
+# will automatically be combined by adding all execution counts.
+#
+# Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
+# is compressed using GZIP. If available, GUNZIP will be used to decompress
+# this file.
+#
+# Die on error.
+#
+sub read_info_file($)
+{
+ my $tracefile = $_[0]; # Name of tracefile
+ my %result; # Resulting hash: file -> data
+ my $data; # Data handle for current entry
+ my $testdata; # " "
+ my $testcount; # " "
+ my $sumcount; # " "
+ my $funcdata; # " "
+ my $checkdata; # " "
+ my $testfncdata;
+ my $testfnccount;
+ my $sumfnccount;
+ my $testbrdata;
+ my $testbrcount;
+ my $sumbrcount;
+ my $line; # Current line read from .info file
+ my $testname; # Current test name
+ my $filename; # Current filename
+ my $hitcount; # Count for lines hit
+ my $count; # Execution count of current line
+ my $negative; # If set, warn about negative counts
+ my $changed_testname; # If set, warn about changed testname
+ my $line_checksum; # Checksum of current line
+ my $notified_about_relative_paths;
+ local *INFO_HANDLE; # Filehandle for .info file
+
+ info("Reading data file $tracefile\n");
+
+ # Check if file exists and is readable
+ stat($tracefile);
+ if (!(-r _))
+ {
+ die("ERROR: cannot read file $tracefile!\n");
+ }
+
+ # Check if this is really a plain file
+ if (!(-f _))
+ {
+ die("ERROR: not a plain file: $tracefile!\n");
+ }
+
+ # Check for .gz extension
+ if ($tracefile =~ /\.gz$/)
+ {
+ # Check for availability of GZIP tool
+ system_no_output(1, "gunzip" ,"-h")
+ and die("ERROR: gunzip command not available!\n");
+
+ # Check integrity of compressed file
+ system_no_output(1, "gunzip", "-t", $tracefile)
+ and die("ERROR: integrity check failed for ".
+ "compressed file $tracefile!\n");
+
+ # Open compressed file
+ open(INFO_HANDLE, "-|", "gunzip -c '$tracefile'")
+ or die("ERROR: cannot start gunzip to decompress ".
+ "file $tracefile!\n");
+ }
+ else
+ {
+ # Open decompressed file
+ open(INFO_HANDLE, "<", $tracefile)
+ or die("ERROR: cannot read file $tracefile!\n");
+ }
+
+ $testname = "";
+ while (<INFO_HANDLE>)
+ {
+ chomp($_);
+ $line = $_;
+
+ # Switch statement
+ foreach ($line)
+ {
+ /^TN:([^,]*)(,diff)?/ && do
+ {
+ # Test name information found
+ $testname = defined($1) ? $1 : "";
+ if ($testname =~ s/\W/_/g)
+ {
+ $changed_testname = 1;
+ }
+ $testname .= $2 if (defined($2));
+ last;
+ };
+
+ /^[SK]F:(.*)/ && do
+ {
+ # Filename information found
+ # Retrieve data for new entry
+ $filename = File::Spec->rel2abs($1, $root);
+
+ if (!File::Spec->file_name_is_absolute($1) &&
+ !$notified_about_relative_paths)
+ {
+ info("Resolved relative source file ".
+ "path \"$1\" with CWD to ".
+ "\"$filename\".\n");
+ $notified_about_relative_paths = 1;
+ }
+
+ $data = $result{$filename};
+ ($testdata, $sumcount, $funcdata, $checkdata,
+ $testfncdata, $sumfnccount, $testbrdata,
+ $sumbrcount) =
+ get_info_entry($data);
+
+ if (defined($testname))
+ {
+ $testcount = $testdata->{$testname};
+ $testfnccount = $testfncdata->{$testname};
+ $testbrcount = $testbrdata->{$testname};
+ }
+ else
+ {
+ $testcount = {};
+ $testfnccount = {};
+ $testbrcount = {};
+ }
+ last;
+ };
+
+ /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
+ {
+ # Fix negative counts
+ $count = $2 < 0 ? 0 : $2;
+ if ($2 < 0)
+ {
+ $negative = 1;
+ }
+ # Execution count found, add to structure
+ # Add summary counts
+ $sumcount->{$1} += $count;
+
+ # Add test-specific counts
+ if (defined($testname))
+ {
+ $testcount->{$1} += $count;
+ }
+
+ # Store line checksum if available
+ if (defined($3))
+ {
+ $line_checksum = substr($3, 1);
+
+ # Does it match a previous definition
+ if (defined($checkdata->{$1}) &&
+ ($checkdata->{$1} ne
+ $line_checksum))
+ {
+ die("ERROR: checksum mismatch ".
+ "at $filename:$1\n");
+ }
+
+ $checkdata->{$1} = $line_checksum;
+ }
+ last;
+ };
+
+ /^FN:(\d+),([^,]+)/ && do
+ {
+ last if (!$func_coverage);
+
+ # Function data found, add to structure
+ $funcdata->{$2} = $1;
+
+ # Also initialize function call data
+ if (!defined($sumfnccount->{$2})) {
+ $sumfnccount->{$2} = 0;
+ }
+ if (defined($testname))
+ {
+ if (!defined($testfnccount->{$2})) {
+ $testfnccount->{$2} = 0;
+ }
+ }
+ last;
+ };
+
+ /^FNDA:(\d+),([^,]+)/ && do
+ {
+ last if (!$func_coverage);
+ # Function call count found, add to structure
+ # Add summary counts
+ $sumfnccount->{$2} += $1;
+
+ # Add test-specific counts
+ if (defined($testname))
+ {
+ $testfnccount->{$2} += $1;
+ }
+ last;
+ };
+
+ /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
+ # Branch coverage data found
+ my ($line, $block, $branch, $taken) =
+ ($1, $2, $3, $4);
+
+ last if (!$br_coverage);
+ $sumbrcount->{$line} .=
+ "$block,$branch,$taken:";
+
+ # Add test-specific counts
+ if (defined($testname)) {
+ $testbrcount->{$line} .=
+ "$block,$branch,$taken:";
+ }
+ last;
+ };
+
+ /^end_of_record/ && do
+ {
+ # Found end of section marker
+ if ($filename)
+ {
+ # Store current section data
+ if (defined($testname))
+ {
+ $testdata->{$testname} =
+ $testcount;
+ $testfncdata->{$testname} =
+ $testfnccount;
+ $testbrdata->{$testname} =
+ $testbrcount;
+ }
+
+ set_info_entry($data, $testdata,
+ $sumcount, $funcdata,
+ $checkdata, $testfncdata,
+ $sumfnccount,
+ $testbrdata,
+ $sumbrcount);
+ $result{$filename} = $data;
+ last;
+ }
+ };
+
+ # default
+ last;
+ }
+ }
+ close(INFO_HANDLE);
+
+ # Calculate lines_found and lines_hit for each file
+ foreach $filename (keys(%result))
+ {
+ $data = $result{$filename};
+
+ ($testdata, $sumcount, undef, undef, $testfncdata,
+ $sumfnccount, $testbrdata, $sumbrcount) =
+ get_info_entry($data);
+
+ # Filter out empty files
+ if (scalar(keys(%{$sumcount})) == 0)
+ {
+ delete($result{$filename});
+ next;
+ }
+ # Filter out empty test cases
+ foreach $testname (keys(%{$testdata}))
+ {
+ if (!defined($testdata->{$testname}) ||
+ scalar(keys(%{$testdata->{$testname}})) == 0)
+ {
+ delete($testdata->{$testname});
+ delete($testfncdata->{$testname});
+ }
+ }
+
+ $data->{"found"} = scalar(keys(%{$sumcount}));
+ $hitcount = 0;
+
+ foreach (keys(%{$sumcount}))
+ {
+ if ($sumcount->{$_} > 0) { $hitcount++; }
+ }
+
+ $data->{"hit"} = $hitcount;
+
+ # Get found/hit values for function call data
+ $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
+ $hitcount = 0;
+
+ foreach (keys(%{$sumfnccount})) {
+ if ($sumfnccount->{$_} > 0) {
+ $hitcount++;
+ }
+ }
+ $data->{"f_hit"} = $hitcount;
+
+ # Combine branch data for the same branches
+ (undef, $data->{"b_found"}, $data->{"b_hit"}) = compress_brcount($sumbrcount);
+ foreach $testname (keys(%{$testbrdata})) {
+ compress_brcount($testbrdata->{$testname});
+ }
+ }
+
+ if (scalar(keys(%result)) == 0)
+ {
+ die("ERROR: no valid records found in tracefile $tracefile\n");
+ }
+ if ($negative)
+ {
+ warn("WARNING: negative counts found in tracefile ".
+ "$tracefile\n");
+ }
+ if ($changed_testname)
+ {
+ warn("WARNING: invalid characters removed from testname in ".
+ "tracefile $tracefile\n");
+ }
+
+ return(\%result);
+}
+
+sub print_simplified_info
+{
+ for my $key (keys(%info_data))
+ {
+ print "K $key: \n";
+ my $sumcountref = $info_data{$key}->{"sum"};
+ for my $line (sort{$a<=>$b}(keys(%$sumcountref)))
+ {
+ print "L $line: $sumcountref->{$line}\n";
+ }
+ }
+}
+
+# From genhtml:
+#
+# get_info_entry(hash_ref)
+#
+# Retrieve data from an entry of the structure generated by read_info_file().
+# Return a list of references to hashes:
+# (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
+# ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
+# functions found, functions hit)
+#
+
+sub get_info_entry($)
+{
+ my $testdata_ref = $_[0]->{"test"};
+ my $sumcount_ref = $_[0]->{"sum"};
+ my $funcdata_ref = $_[0]->{"func"};
+ my $checkdata_ref = $_[0]->{"check"};
+ my $testfncdata = $_[0]->{"testfnc"};
+ my $sumfnccount = $_[0]->{"sumfnc"};
+ my $testbrdata = $_[0]->{"testbr"};
+ my $sumbrcount = $_[0]->{"sumbr"};
+ my $lines_found = $_[0]->{"found"};
+ my $lines_hit = $_[0]->{"hit"};
+ my $fn_found = $_[0]->{"f_found"};
+ my $fn_hit = $_[0]->{"f_hit"};
+ my $br_found = $_[0]->{"b_found"};
+ my $br_hit = $_[0]->{"b_hit"};
+
+ return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
+ $testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
+ $lines_found, $lines_hit, $fn_found, $fn_hit,
+ $br_found, $br_hit);
+}
+
+
+# From genhtml:
+#
+# set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
+# checkdata_ref, testfncdata_ref, sumfcncount_ref,
+# testbrdata_ref, sumbrcount_ref[,lines_found,
+# lines_hit, f_found, f_hit, $b_found, $b_hit])
+#
+# Update the hash referenced by HASH_REF with the provided data references.
+#
+
+sub set_info_entry($$$$$$$$$;$$$$$$)
+{
+ my $data_ref = $_[0];
+
+ $data_ref->{"test"} = $_[1];
+ $data_ref->{"sum"} = $_[2];
+ $data_ref->{"func"} = $_[3];
+ $data_ref->{"check"} = $_[4];
+ $data_ref->{"testfnc"} = $_[5];
+ $data_ref->{"sumfnc"} = $_[6];
+ $data_ref->{"testbr"} = $_[7];
+ $data_ref->{"sumbr"} = $_[8];
+
+ if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
+ if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
+ if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
+ if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
+ if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
+ if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
+}
+
+# From genhtml:
+#
+# combine_info_entries(entry_ref1, entry_ref2, filename)
+#
+# Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
+# Return reference to resulting hash.
+#
+sub combine_info_entries($$$)
+{
+ my $entry1 = $_[0]; # Reference to hash containing first entry
+ my $testdata1;
+ my $sumcount1;
+ my $funcdata1;
+ my $checkdata1;
+ my $testfncdata1;
+ my $sumfnccount1;
+ my $testbrdata1;
+ my $sumbrcount1;
+
+ my $entry2 = $_[1]; # Reference to hash containing second entry
+ my $testdata2;
+ my $sumcount2;
+ my $funcdata2;
+ my $checkdata2;
+ my $testfncdata2;
+ my $sumfnccount2;
+ my $testbrdata2;
+ my $sumbrcount2;
+
+ my %result; # Hash containing combined entry
+ my %result_testdata;
+ my $result_sumcount = {};
+ my $result_funcdata;
+ my $result_testfncdata;
+ my $result_sumfnccount;
+ my $result_testbrdata;
+ my $result_sumbrcount;
+ my $lines_found;
+ my $lines_hit;
+ my $fn_found;
+ my $fn_hit;
+ my $br_found;
+ my $br_hit;
+
+ my $testname;
+ my $filename = $_[2];
+
+ # Retrieve data
+ ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
+ $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
+ ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
+ $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
+
+# # Merge checksums
+# $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
+#
+# # Combine funcdata
+# $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
+#
+# # Combine function call count data
+# $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
+# ($result_sumfnccount, $fn_found, $fn_hit) =
+# add_fnccount($sumfnccount1, $sumfnccount2);
+#
+# # Combine branch coverage data
+# $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
+# ($result_sumbrcount, $br_found, $br_hit) =
+# combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
+#
+ # Combine testdata
+ foreach $testname (keys(%{$testdata1}))
+ {
+ if (defined($testdata2->{$testname}))
+ {
+ # testname is present in both entries, requires
+ # combination
+ ($result_testdata{$testname}) =
+ add_counts($testdata1->{$testname},
+ $testdata2->{$testname});
+ }
+ else
+ {
+ # testname only present in entry1, add to result
+ $result_testdata{$testname} = $testdata1->{$testname};
+ }
+
+ # update sum count hash
+ ($result_sumcount, $lines_found, $lines_hit) =
+ add_counts($result_sumcount,
+ $result_testdata{$testname});
+ }
+
+ foreach $testname (keys(%{$testdata2}))
+ {
+ # Skip testnames already covered by previous iteration
+ if (defined($testdata1->{$testname})) { next; }
+
+ # testname only present in entry2, add to result hash
+ $result_testdata{$testname} = $testdata2->{$testname};
+
+ # update sum count hash
+ ($result_sumcount, $lines_found, $lines_hit) =
+ add_counts($result_sumcount,
+ $result_testdata{$testname});
+ }
+
+ # Calculate resulting sumcount
+
+ # Store result
+ set_info_entry(\%result, \%result_testdata, $result_sumcount,
+ $result_funcdata, $checkdata1, $result_testfncdata,
+ $result_sumfnccount, $result_testbrdata,
+ $result_sumbrcount, $lines_found, $lines_hit,
+ $fn_found, $fn_hit, $br_found, $br_hit);
+
+ return(\%result);
+}
+
+# From genhtml:
+#
+# combine_info_files(info_ref1, info_ref2)
+#
+# Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
+# reference to resulting hash.
+#
+sub combine_info_files($$)
+{
+ my %hash1 = %{$_[0]};
+ my %hash2 = %{$_[1]};
+ my $filename;
+
+ foreach $filename (keys(%hash2))
+ {
+ if ($hash1{$filename})
+ {
+ # Entry already exists in hash1, combine them
+ $hash1{$filename} =
+ combine_info_entries($hash1{$filename},
+ $hash2{$filename},
+ $filename);
+ }
+ else
+ {
+ # Entry is unique in both hashes, simply add to
+ # resulting hash
+ $hash1{$filename} = $hash2{$filename};
+ }
+ }
+
+ return(\%hash1);
+}
+
+# From genhtml:
+#
+# add_counts(data1_ref, data2_ref)
+#
+# DATA1_REF and DATA2_REF are references to hashes containing a mapping
+#
+# line number -> execution count
+#
+# Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
+# is a reference to a hash containing the combined mapping in which
+# execution counts are added.
+#
+sub add_counts($$)
+{
+ my $data1_ref = $_[0]; # Hash 1
+ my $data2_ref = $_[1]; # Hash 2
+ my %result; # Resulting hash
+ my $line; # Current line iteration scalar
+ my $data1_count; # Count of line in hash1
+ my $data2_count; # Count of line in hash2
+ my $found = 0; # Total number of lines found
+ my $hit = 0; # Number of lines with a count > 0
+
+ foreach $line (keys(%$data1_ref))
+ {
+ $data1_count = $data1_ref->{$line};
+ $data2_count = $data2_ref->{$line};
+
+ # Add counts if present in both hashes
+ if (defined($data2_count)) { $data1_count += $data2_count; }
+
+ # Store sum in %result
+ $result{$line} = $data1_count;
+
+ $found++;
+ if ($data1_count > 0) { $hit++; }
+ }
+
+ # Add lines unique to data2_ref
+ foreach $line (keys(%$data2_ref))
+ {
+ # Skip lines already in data1_ref
+ if (defined($data1_ref->{$line})) { next; }
+
+ # Copy count from data2_ref
+ $result{$line} = $data2_ref->{$line};
+
+ $found++;
+ if ($result{$line} > 0) { $hit++; }
+ }
+
+ return (\%result, $found, $hit);
+}
+
+
+# From genhtml:
+sub compress_brcount($)
+{
+ my ($brcount) = @_;
+ my $db;
+
+ $db = brcount_to_db($brcount);
+ return db_to_brcount($db, $brcount);
+}
+
+#
+# brcount_to_db(brcount)
+#
+# Convert brcount data to the following format:
+#
+# db: line number -> block hash
+# block hash: block number -> branch hash
+# branch hash: branch number -> taken value
+#
+
+sub brcount_to_db($)
+{
+ my ($brcount) = @_;
+ my $line;
+ my $db;
+
+ # Add branches to database
+ foreach $line (keys(%{$brcount})) {
+ my $brdata = $brcount->{$line};
+
+ foreach my $entry (split(/:/, $brdata)) {
+ my ($block, $branch, $taken) = split(/,/, $entry);
+ my $old = $db->{$line}->{$block}->{$branch};
+
+ if (!defined($old) || $old eq "-") {
+ $old = $taken;
+ } elsif ($taken ne "-") {
+ $old += $taken;
+ }
+
+ $db->{$line}->{$block}->{$branch} = $old;
+ }
+ }
+
+ return $db;
+}
+
+
+#
+# db_to_brcount(db[, brcount])
+#
+# Convert branch coverage data back to brcount format. If brcount is specified,
+# the converted data is directly inserted in brcount.
+#
+
+sub db_to_brcount($;$)
+{
+ my ($db, $brcount) = @_;
+ my $line;
+ my $br_found = 0;
+ my $br_hit = 0;
+
+ # Convert database back to brcount format
+ foreach $line (sort({$a <=> $b} keys(%{$db}))) {
+ my $ldata = $db->{$line};
+ my $brdata;
+ my $block;
+
+ foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
+ my $bdata = $ldata->{$block};
+ my $branch;
+
+ foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
+ my $taken = $bdata->{$branch};
+
+ $br_found++;
+ $br_hit++ if ($taken ne "-" && $taken > 0);
+ $brdata .= "$block,$branch,$taken:";
+ }
+ }
+ $brcount->{$line} = $brdata;
+ }
+
+ return ($brcount, $br_found, $br_hit);
+}
+
+
+#
+# brcount_db_combine(db1, db2, op)
+#
+# db1 := db1 op db2, where
+# db1, db2: brcount data as returned by brcount_to_db
+# op: one of $BR_ADD and BR_SUB
+#
+sub brcount_db_combine($$$)
+{
+ my ($db1, $db2, $op) = @_;
+
+ foreach my $line (keys(%{$db2})) {
+ my $ldata = $db2->{$line};
+
+ foreach my $block (keys(%{$ldata})) {
+ my $bdata = $ldata->{$block};
+
+ foreach my $branch (keys(%{$bdata})) {
+ my $taken = $bdata->{$branch};
+ my $new = $db1->{$line}->{$block}->{$branch};
+
+ if (!defined($new) || $new eq "-") {
+ $new = $taken;
+ } elsif ($taken ne "-") {
+ if ($op == $BR_ADD) {
+ $new += $taken;
+ } elsif ($op == $BR_SUB) {
+ $new -= $taken;
+ $new = 0 if ($new < 0);
+ }
+ }
+
+ $db1->{$line}->{$block}->{$branch} = $new;
+ }
+ }
+ }
+}
+
+# From genhtml
+sub info(@)
+{
+ if($debug)
+ {
+ # Print info string
+ printf(@_);
+ }
+}
+
+# NEW STUFF
+
## Format per file, repeated, no linebreak
# <diffcmd>
# index c1..c2 c3
# 3 lines of context
#
# output:
+# <dali-source-file>: source / header files in dali/dali-toolkit
+# \%filter: <dali-source-file> -> \%filedata
+# %filedata: "patch" -> \@checklines
+# "b_lines" -> \%b_lines
+# @checklines: vector of \[start, length] # line numbers of new/modified lines
+# %b_lines: <line-number> -> patch line in b-file.
sub parse_diff
{
my $patchref = shift;
}
}
- return {%filter};
+ return {%filter};#copy? - test and fixme
}
sub show_patch_lines
}
}
-sub get_gcno_file
-{
- # Assumes test cases have been run, and "make rename_cov_data" has been executed
-
- my $file = shift;
- my ($name, $path, $suffix) = fileparse($file, (".c", ".cpp", ".h"));
- my $gcno_file = $repo->wc_path() . "/build/tizen/.cov/$name.gcno";
-
- # Note, will translate headers to their source's object, which
- # may miss execution code in the headers (e.g. inlines are usually
- # not all used in the implementation, and require getting coverage
- # from test cases.
-
- if( -f $gcno_file )
- {
- my $gcno_st = stat($gcno_file);
- my $fq_file = $repo->wc_path() . $file;
- my $src_st = stat($fq_file);
- if($gcno_st->ctime < $src_st->mtime)
- {
- print "WARNING: GCNO $gcno_file older than SRC $fq_file\n";
- $gcno_file="";
- }
-
- }
- else
- {
- print("WARNING: No equivalent gcno file for $file\n");
- }
- return $gcno_file;
-}
-
-our %gcovfiles=();
-sub get_coverage
-{
- my $file = shift;
- my $filesref = shift;
- print("get_coverage($file)\n") if $debug;
- my $gcno_file = get_gcno_file($file);
- my @gcov_files = ();
- my $gcovfile;
- if( $gcno_file )
- {
- print "Running gcov on $gcno_file:\n" if $debug;
- open( my $fh, "gcov --preserve-paths $gcno_file |") || die "Can't run gcov:$!\n";
- while( <$fh> )
- {
- print $_ if $debug>=3;
- chomp;
- if( m!'(.*\.gcov)'$! )
- {
- my $coverage_file = $1; # File has / replaced with # and .. replaced with ^
- my $source_file = $coverage_file;
- $source_file =~ s!\^!..!g; # Change ^ to ..
- $source_file =~ s!\#!/!g; # change #'s to /s
- $source_file =~ s!.gcov$!!; # Strip off .gcov suffix
-
- print "Matching $file against $source_file\n" if $debug >= 3;
- # Only want the coverage files matching source file:
- if(index( $source_file, $file ) > 0 )
- {
- $gcovfile = $coverage_file;
- # Some header files do not produce an equivalent gcov file so we shouldn't parse them
- if(($source_file =~ /\.h$/) && (! -e $gcovfile))
- {
- print "Omitting Header: $source_file\n" if $debug;
- $gcovfile = ""
- }
- last;
- }
- }
- }
- close($fh);
-
- if($gcovfile)
- {
- if($gcovfiles{$gcovfile} == undef)
- {
- # Only parse a gcov file once
- $gcovfiles{$gcovfile}->{"seen"}=1;
-
- print "Getting coverage data from $gcovfile\n" if $debug;
-
- open( FH, "< $gcovfile" ) || die "Can't open $gcovfile for reading:$!\n";
- while(<FH>)
- {
- my ($cov, $line, @code ) = split( /:/, $_ );
- $cov =~ s/^\s+//; # Strip leading space
- $line =~ s/^\s+//;
- my $code=join(":", @code);
- if($cov =~ /\#/)
- {
- # There is no coverage data for these executable lines
- $gcovfiles{$gcovfile}->{"uncovered"}->{$line}++;
- $gcovfiles{$gcovfile}->{"src"}->{$line}=$code;
- }
- elsif( $cov ne "-" && looks_like_number($cov) && $cov > 0 )
- {
- $gcovfiles{$gcovfile}->{"covered"}->{$line}=$cov;
- $gcovfiles{$gcovfile}->{"src"}->{$line}=$code;
- }
- else
- {
- # All other lines are not executable.
- $gcovfiles{$gcovfile}->{"src"}->{$line}=$code;
- }
- }
- close( FH );
- }
- $filesref->{$file}->{"coverage"} = $gcovfiles{$gcovfile}; # store hashref
- }
- else
- {
- # No gcov output - the gcno file produced no coverage of the source/header
- # Probably means that there is no coverage for the file (with the given
- # test case - there may be some somewhere, but for the sake of speed, don't
- # check (yet).
- }
- }
-}
-
-# Run the git diff command to get the patch, then check the coverage
-# output for the patch.
+# Run the git diff command to get the patch
+# Output - see parse_diff
sub run_diff
{
- #print "run_diff(" . join(" ", @_) . ")\n";
my ($fh, $c) = $repo->command_output_pipe(@_);
our @patch=();
while(<$fh>)
# @patch has slurped diff for all files...
my $filesref = parse_diff ( \@patch );
- show_patch_lines($filesref) if $debug;
-
- print "Checking coverage:\n" if $debug;
-
- my $cwd=getcwd();
- chdir ".cov" || die "Can't find $cwd/.cov:$!\n";
+ show_patch_lines($filesref) if $debug>1;
- for my $file (keys(%$filesref))
- {
- my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
- next if($path !~ /^dali/);
- if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
- {
- get_coverage($file, $filesref);
- }
- }
- chdir $cwd;
return $filesref;
}
my $uncovered_lines = 0;
my $patchref = $filesref->{$file}->{"patch"};
- my $coverage_ref = $filesref->{$file}->{"coverage"};
- if( $coverage_ref )
+
+ my $abs_filename = File::Spec->rel2abs($file, $root);
+ my $sumcountref = $info_data{$abs_filename}->{"sum"};
+
+ if( $sumcountref )
{
for my $patch (@$patchref)
{
for(my $i = 0; $i < $patch->[1]; $i++ )
{
my $line = $i + $patch->[0];
- if($coverage_ref->{"covered"}->{$line})
- {
- $covered_lines++;
- $total_covered_lines++;
- }
- if($coverage_ref->{"uncovered"}->{$line})
+ if(exists($sumcountref->{$line}))
{
- $uncovered_lines++;
- $total_uncovered_lines++;
+ if( $sumcountref->{$line} > 0 )
+ {
+ $covered_lines++;
+ $total_covered_lines++;
+ }
+ else
+ {
+ $uncovered_lines++;
+ $total_uncovered_lines++;
+ }
}
}
}
- $coverage_ref->{"covered_lines"} = $covered_lines;
- $coverage_ref->{"uncovered_lines"} = $uncovered_lines;
+ $filesref->{$file}->{"covered_lines"} = $covered_lines;
+ $filesref->{$file}->{"uncovered_lines"} = $uncovered_lines;
my $total = $covered_lines + $uncovered_lines;
my $percent = 0;
if($total > 0)
{
$percent = $covered_lines / $total;
}
- $coverage_ref->{"percent_covered"} = 100 * $percent;
+ $filesref->{$file}->{"percent_covered"} = 100 * $percent;
+ }
+ else
+ {
+ print "Can't find coverage data for $abs_filename\n";
}
}
my $total_exec = $total_covered_lines + $total_uncovered_lines;
return [ $total_exec, $percent ];
}
+#
+#
sub patch_output
{
my $filesref = shift;
my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
next if($path !~ /^dali/);
- my $patchref = $filesref->{$file}->{"patch"};
- my $b_lines_ref = $filesref->{$file}->{"b_lines"};
- my $coverage_ref = $filesref->{$file}->{"coverage"};
+ my $fileref = $filesref->{$file};
+ my $patchref = $fileref->{"patch"};
+ my $b_lines_ref = $fileref->{"b_lines"};
+
+ my $abs_filename = File::Spec->rel2abs($file, $root);
+ my $sumcountref = $info_data{$abs_filename}->{"sum"};
+
print BOLD, "$file ";
- if($coverage_ref)
+ if($fileref)
{
- if( $coverage_ref->{"covered_lines"} > 0
+ if( $fileref->{"covered_lines"} > 0
||
- $coverage_ref->{"uncovered_lines"} > 0 )
+ $fileref->{"uncovered_lines"} > 0 )
{
- print GREEN, "Covered: " . $coverage_ref->{"covered_lines"}, RED, " Uncovered: " . $coverage_ref->{"uncovered_lines"}, RESET;
+ print GREEN, "Covered: " . $fileref->{"covered_lines"}, RED, " Uncovered: " . $fileref->{"uncovered_lines"}, RESET;
}
}
else
my $line = $i + $patch->[0];
printf "%-6s ", $line;
- if($coverage_ref)
+ if($sumcountref)
{
my $color;
- if($coverage_ref->{"covered"}->{$line})
+ if(exists($sumcountref->{$line}))
{
- $color=GREEN;
- }
- elsif($coverage_ref->{"uncovered"}->{$line})
- {
- $color=BOLD . RED;
+ if($sumcountref->{$line} > 0)
+ {
+ $color=GREEN;
+ }
+ else
+ {
+ $color=BOLD . RED;
+ }
}
else
{
- $color=BLACK;
+ $color=CYAN;
}
- my $src=$coverage_ref->{"src"}->{$line};
- chomp($src);
+ my $src = $b_lines_ref->{$line};
print $color, "$src\n", RESET;
}
else
{
- # We don't have coverage data, so print it from the patch instead.
my $src = $b_lines_ref->{$line};
print "$src\n";
}
}
}
-
+#
+#
sub patch_html_output
{
my $filesref = shift;
- my $html = HTML::Element->new('html');
- my $head = HTML::Element->new('head');
- my $title = HTML::Element->new('title');
- $title->push_content("Patch Coverage");
- $head->push_content($title, "\n");
- $html->push_content($head, "\n");
+ open( my $filehandle, ">", $opt_output ) || die "Can't open $opt_output for writing:$!\n";
- my $body = HTML::Element->new('body');
- $body->attr('bgcolor', "white");
+ my $OUTPUT_FH = select;
+ select $filehandle;
+ print <<EOH;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+"http://www.w3.org/TR/REC-html40/loose.dtd">
+<html>
+<head>
+<title>Patch Coverage</title>
+</head>
+<body bgcolor="white">
+EOH
- foreach my $file (sort(keys(%$filesref)))
+ foreach my $file (keys(%$filesref))
{
my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
next if($path !~ /^dali/);
- my $patchref = $filesref->{$file}->{"patch"};
- my $b_lines_ref = $filesref->{$file}->{"b_lines"};
- my $coverage_ref = $filesref->{$file}->{"coverage"};
-
- my $header = HTML::Element->new('h2');
- $header->push_content($file);
- $body->push_content($header);
- $body->push_content("\n");
- if($coverage_ref)
+ my $fileref = $filesref->{$file};
+ my $patchref = $fileref->{"patch"};
+ my $b_lines_ref = $fileref->{"b_lines"};
+
+ my $abs_filename = File::Spec->rel2abs($file, $root);
+ my $sumcountref = $info_data{$abs_filename}->{"sum"};
+
+ print "<h2>$file</h2>\n";
+
+ if($fileref)
{
- if( $coverage_ref->{"covered_lines"} > 0
+ if( $fileref->{"covered_lines"} > 0
||
- $coverage_ref->{"uncovered_lines"} > 0 )
+ $fileref->{"uncovered_lines"} > 0 )
{
- my $para = HTML::Element->new('p');
- my $covered = HTML::Element->new('span');
- $covered->attr('style', "color:green;");
- $covered->push_content("Covered: " . $coverage_ref->{"covered_lines"} );
- $para->push_content($covered);
-
- my $para2 = HTML::Element->new('p');
- my $uncovered = HTML::Element->new('span');
- $uncovered->attr('style', "color:red;");
- $uncovered->push_content("Uncovered: " . $coverage_ref->{"uncovered_lines"} );
- $para2->push_content($uncovered);
- $body->push_content($para, $para2);
- }
- else
- {
- #print "coverage ref exists for $file:\n" . Data::Dumper::Dumper($coverage_ref) . "\n";
+ print "<p style=\"color:green;\">Covered: " .
+ $fileref->{"covered_lines"} . "<p>" .
+ "<p style=\"color:red;\">Uncovered: " .
+ $fileref->{"uncovered_lines"} . "</span></p>";
}
}
else
{
- my $para = HTML::Element->new('p');
- my $span = HTML::Element->new('span');
+ print "<p>";
+ my $span=0;
if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
{
- $span->attr('style', "color:red;");
+ print "<span style=\"color:red;\">";
+ $span=1;
}
- $span->push_content("No coverage found");
- $para->push_content($span);
- $body->push_content($para);
+ print "No coverage found";
+ print "</span>" if $span;
}
+ print "</p>";
for my $patch (@$patchref)
{
{
$hunkstr .= " - " . ($patch->[0]+$patch->[1]-1);
}
+ print "<p style=\"font-weight:bold;\">" . $hunkstr . "</p>";
- my $para = HTML::Element->new('p');
- my $span = HTML::Element->new('span');
- $span->attr('style', "font-weight:bold;");
- $span->push_content($hunkstr);
- $para->push_content($span);
- $body->push_content($para);
-
- my $codeHunk = HTML::Element->new('pre');
+ print "<pre>";
for(my $i = 0; $i < $patch->[1]; $i++ )
{
my $line = $i + $patch->[0];
my $num_line_digits=log($line)/log(10);
for $i (0..(6-$num_line_digits-1))
{
- $codeHunk->push_content(" ");
+ print " ";
}
+ print "$line ";
- $codeHunk->push_content("$line ");
-
- my $srcLine = HTML::Element->new('span');
- if($coverage_ref)
+ if($sumcountref)
{
my $color;
-
- if($coverage_ref->{"covered"}->{$line})
+ if(exists($sumcountref->{$line}))
{
- $srcLine->attr('style', "color:green;");
- }
- elsif($coverage_ref->{"uncovered"}->{$line})
- {
- $srcLine->attr('style', "color:red;font-weight:bold;");
+ if($sumcountref->{$line} > 0)
+ {
+ print("<span style=\"color:green;\">");
+ }
+ else
+ {
+ print("<span style=\"color:red;font-weight:bold;\">");
+ }
}
else
{
- $srcLine->attr('style', "color:black;font-weight:normal;");
+ print("<span style=\"color:black;font-weight:normal;\">");
}
- my $src=$coverage_ref->{"src"}->{$line};
+ my $src=$b_lines_ref->{$line};
chomp($src);
- $srcLine->push_content($src);
+ print "$src</span>\n";
}
else
{
- # We don't have coverage data, so print it from the patch instead.
my $src = $b_lines_ref->{$line};
- $srcLine->attr('style', "color:black;font-weight:normal;");
- $srcLine->push_content($src);
+ print "$src\n";
}
- $codeHunk->push_content($srcLine, "\n");
}
- $body->push_content($codeHunk, "\n");
+ print "<\pre>\n";
}
}
- $body->push_content(HTML::Element->new('hr'));
- $html->push_content($body, "\n");
- open( my $filehandle, ">", $opt_output ) || die "Can't open $opt_output for writing:$!\n";
-
- print $filehandle <<EOH;
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
-"http://www.w3.org/TR/REC-html40/loose.dtd">
-EOH
-;
- print $filehandle $html->as_HTML();
+ print $filehandle "<hr>\n</body>\n</html>\n";
close $filehandle;
+ select $OUTPUT_FH;
}
## MAIN ##
################################################################################
-my $cwd = getcwd();
-chdir $repo->wc_path();
-chdir "build/tizen";
-`make rename_cov_data`;
+my $cwd = getcwd(); # expect this to be automated-tests folder
-my @cmd=('--no-pager','diff','--no-ext-diff','-U0','--no-color');
+# execute coverage.sh, generating build/tizen/dali.info from lib, and
+# *.dir/dali.info. Don't generate html
+print `./coverage.sh -n`;
+chdir "..";
+$root = getcwd();
+
+our %info_data; # Hash of all data from .info files
+my @info_files = split(/\n/, `find . -name dali.info`);
+my %new_info;
+
+# Read in all specified .info files
+foreach (@info_files)
+{
+ %new_info = %{read_info_file($_)};
+
+ # Combine %new_info with %info_data
+ %info_data = %{combine_info_files(\%info_data, \%new_info)};
+}
+
+# Generate git diff command
+my @cmd=('--no-pager','diff','--no-ext-diff','-U0','--no-color');
my $status = $repo->command("status", "-s");
+
if( $status eq "" && !scalar(@ARGV))
{
# There are no changes in the index or working tree, and
}
push @cmd, @ARGV;
+
+# Execute diff & coverage from root directory
my $filesref = run_diff(@cmd);
chdir $cwd;
exit 0; # Exit with no error.
}
+#print_simplified_info() if $debug;
+#exit 0;
+
my $percentref = calc_patch_coverage_percentage($filesref);
if($percentref->[0] == 0)
{
{
gResourceReadySignalCounter++;
- if( gResourceReadySignalCounter == 1 )
+ if(control.GetVisualResourceStatus(ImageView::Property::IMAGE) == Visual::ResourceStatus::READY)
{
- // Set image twice
- ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
- ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
+ if( gResourceReadySignalCounter == 1 )
+ {
+ // Set image twice
+ // It makes the first new visual be deleted immediately
+ ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
+ ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
+ }
+ }
+ else if(control.GetVisualResourceStatus(ImageView::Property::IMAGE) == Visual::ResourceStatus::FAILED)
+ {
+ // Make the resource ready immediately
+ control[ImageView::Property::IMAGE] = TEST_RESOURCE_DIR "/svg1.svg";
}
}
DALI_TEST_EQUALS( imageView.IsResourceReady(), true, TEST_LOCATION );
+ // Reset count
+ gResourceReadySignalCounter = 0;
+
+ imageView[ImageView::Property::IMAGE] = "invalid.jpg";
+
+ DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+
+ application.SendNotification();
+ application.Render();
+
+ // Run idle callback
+ application.RunIdles();
+
+ DALI_TEST_EQUALS( gResourceReadySignalCounter, 2, TEST_LOCATION );
+
+ DALI_TEST_EQUALS( imageView.IsResourceReady(), true, TEST_LOCATION );
+
END_TEST;
}
#include <dali/devel-api/scripting/scripting.h>
#include <dali/integration-api/debug.h>
#include <dali/public-api/object/type-registry-helper.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
#include <cstring>
#include <limits>
mDownFocusableActorId( -1 ),
mStyleName(""),
mBackgroundColor(Color::TRANSPARENT),
- mStartingPinchScale( NULL ),
+ mStartingPinchScale(nullptr),
mMargin( 0, 0, 0, 0 ),
mPadding( 0, 0, 0, 0 ),
mKeyEventSignal(),
mLongPressGestureDetector(),
mTooltip( NULL ),
mInputMethodContext(),
+ mIdleCallback(nullptr),
mFlags( Control::ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ),
mIsKeyboardNavigationSupported( false ),
- mIsKeyboardFocusGroup( false )
+ mIsKeyboardFocusGroup( false ),
+ mIsEmittingResourceReadySignal(false),
+ mNeedToEmitResourceReady(false)
{
}
{
// All gesture detectors will be destroyed so no need to disconnect.
delete mStartingPinchScale;
+
+ if(mIdleCallback && Adaptor::IsAvailable())
+ {
+ // Removes the callback from the callback manager in case the control is destroyed before the callback is executed.
+ Adaptor::Get().RemoveIdle(mIdleCallback);
+ }
}
Control::Impl& Control::Impl::Get( Internal::Control& internalControl )
// Emit signal if all enabled visuals registered by the control are ready.
if( IsResourceReady() )
{
- Dali::Toolkit::Control handle( mControlImpl.GetOwner() );
- mResourceReadySignal.Emit( handle );
+ // Reset the flag
+ mNeedToEmitResourceReady = false;
+
+ EmitResourceReadySignal();
}
}
mControlImpl.RelayoutRequest();
}
+void Control::Impl::EmitResourceReadySignal()
+{
+ if(!mIsEmittingResourceReadySignal)
+ {
+ // Guard against calls to emit the signal during the callback
+ mIsEmittingResourceReadySignal = true;
+
+ // If the signal handler changes visual, it may become ready during this call & therefore this method will
+ // get called again recursively. If so, mNeedToEmitResourceReady is set below, and we act on it after that secondary
+ // invocation has completed by notifying in an Idle callback to prevent further recursion.
+ Dali::Toolkit::Control handle(mControlImpl.GetOwner());
+ mResourceReadySignal.Emit(handle);
+
+ if(mNeedToEmitResourceReady)
+ {
+ // Add idler to emit the signal again
+ if(!mIdleCallback)
+ {
+ // The callback manager takes the ownership of the callback object.
+ mIdleCallback = MakeCallback( this, &Control::Impl::OnIdleCallback);
+ Adaptor::Get().AddIdle(mIdleCallback, false);
+ }
+ }
+
+ mIsEmittingResourceReadySignal = false;
+ }
+ else
+ {
+ mNeedToEmitResourceReady = true;
+ }
+}
+
+void Control::Impl::OnIdleCallback()
+{
+ if(mNeedToEmitResourceReady)
+ {
+ // Reset the flag
+ mNeedToEmitResourceReady = false;
+
+ // A visual is ready so control may need relayouting if staged
+ if(mControlImpl.Self().GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
+ {
+ mControlImpl.RelayoutRequest();
+ }
+
+ EmitResourceReadySignal();
+ }
+
+ // Set the pointer to null as the callback manager deletes the callback after execute it.
+ mIdleCallback = nullptr;
+}
+
} // namespace Internal
} // namespace Toolkit
*/
void RegisterVisual( Property::Index index, Toolkit::Visual::Base& visual, VisualState::Type enabled, DepthIndexValue::Type depthIndexValueSet, int depthIndex = 0 );
+ /**
+ * @brief Emits the resource ready signal.
+ */
+ void EmitResourceReadySignal();
+
+ /**
+ * @brief Callbacks called on idle.
+ */
+ void OnIdleCallback();
+
public:
Control& mControlImpl;
TooltipPtr mTooltip;
InputMethodContext mInputMethodContext;
+ CallbackBase* mIdleCallback; ///< The idle callback to emit the resource ready signal.
ControlBehaviour mFlags : CONTROL_BEHAVIOUR_FLAG_COUNT; ///< Flags passed in from constructor.
bool mIsKeyboardNavigationSupported :1; ///< Stores whether keyboard navigation is supported by the control.
bool mIsKeyboardFocusGroup :1; ///< Stores whether the control is a focus group.
+ bool mIsEmittingResourceReadySignal :1; ///< True during ResourceReady().
+ bool mNeedToEmitResourceReady :1; ///< True if need to emit the resource ready signal again.
RegisteredVisualContainer mRemoveVisuals; ///< List of visuals that are being replaced by another visual once ready
namespace
{
- const unsigned int DEFAULT_RENDERING_BACKEND = Dali::Toolkit::DevelText::DEFAULT_RENDERING_BACKEND;
-
- /**
- * @brief How the text visual should be aligned vertically inside the control.
- *
- * 0.0f aligns the text to the top, 0.5f aligns the text to the center, 1.0f aligns the text to the bottom.
- * The alignment depends on the alignment value of the text label (Use Text::VerticalAlignment enumerations).
- */
- const float VERTICAL_ALIGNMENT_TABLE[ Text::VerticalAlignment::BOTTOM + 1 ] =
- {
- 0.0f, // VerticalAlignment::TOP
- 0.5f, // VerticalAlignment::CENTER
- 1.0f // VerticalAlignment::BOTTOM
- };
-
- const std::string TEXT_FIT_ENABLE_KEY( "enable" );
- const std::string TEXT_FIT_MIN_SIZE_KEY( "minSize" );
- const std::string TEXT_FIT_MAX_SIZE_KEY( "maxSize" );
- const std::string TEXT_FIT_STEP_SIZE_KEY( "stepSize" );
- const std::string TEXT_FIT_FONT_SIZE_TYPE_KEY( "fontSizeType" );
-}
+const unsigned int DEFAULT_RENDERING_BACKEND = Dali::Toolkit::DevelText::DEFAULT_RENDERING_BACKEND;
-namespace
+/**
+ * @brief How the text visual should be aligned vertically inside the control.
+ *
+ * 0.0f aligns the text to the top, 0.5f aligns the text to the center, 1.0f aligns the text to the bottom.
+ * The alignment depends on the alignment value of the text label (Use Text::VerticalAlignment enumerations).
+ */
+const float VERTICAL_ALIGNMENT_TABLE[ Text::VerticalAlignment::BOTTOM + 1 ] =
{
+ 0.0f, // VerticalAlignment::TOP
+ 0.5f, // VerticalAlignment::CENTER
+ 1.0f // VerticalAlignment::BOTTOM
+};
+
+const std::string TEXT_FIT_ENABLE_KEY( "enable" );
+const std::string TEXT_FIT_MIN_SIZE_KEY( "minSize" );
+const std::string TEXT_FIT_MAX_SIZE_KEY( "maxSize" );
+const std::string TEXT_FIT_STEP_SIZE_KEY( "stepSize" );
+const std::string TEXT_FIT_FONT_SIZE_TYPE_KEY( "fontSizeType" );
#if defined ( DEBUG_ENABLED )
- Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
#endif
const Scripting::StringEnum AUTO_SCROLL_STOP_MODE_TABLE[] =
DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION( Toolkit, TextLabel, "textColorAlpha", TEXT_COLOR_ALPHA, TEXT_COLOR, 3 )
DALI_TYPE_REGISTRATION_END()
+/// Parses the property map for the TEXT_FIT property
+void ParseTextFitProperty(Text::ControllerPtr& controller, const Property::Map* propertiesMap)
+{
+ if ( propertiesMap && !propertiesMap->Empty() )
+ {
+ bool enabled = false;
+ float minSize = 0.f;
+ float maxSize = 0.f;
+ float stepSize = 0.f;
+ bool isMinSizeSet = false, isMaxSizeSet = false, isStepSizeSet = false;
+ Controller::FontSizeType type = Controller::FontSizeType::POINT_SIZE;
+
+ const unsigned int numberOfItems = propertiesMap->Count();
+
+ // Parses and applies
+ for( unsigned int index = 0u; index < numberOfItems; ++index )
+ {
+ const KeyValuePair& valueGet = propertiesMap->GetKeyValue( index );
+
+ if( ( Controller::TextFitInfo::Property::TEXT_FIT_ENABLE == valueGet.first.indexKey ) || ( TEXT_FIT_ENABLE_KEY == valueGet.first.stringKey ) )
+ {
+ /// Enable key.
+ enabled = valueGet.second.Get< bool >();
+ }
+ else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MIN_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MIN_SIZE_KEY == valueGet.first.stringKey ) )
+ {
+ /// min size.
+ minSize = valueGet.second.Get< float >();
+ isMinSizeSet = true;
+ }
+ else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MAX_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MAX_SIZE_KEY == valueGet.first.stringKey ) )
+ {
+ /// max size.
+ maxSize = valueGet.second.Get< float >();
+ isMaxSizeSet = true;
+ }
+ else if( ( Controller::TextFitInfo::Property::TEXT_FIT_STEP_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_STEP_SIZE_KEY == valueGet.first.stringKey ) )
+ {
+ /// step size.
+ stepSize = valueGet.second.Get< float >();
+ isStepSizeSet = true;
+ }
+ else if( ( Controller::TextFitInfo::Property::TEXT_FIT_FONT_SIZE_TYPE == valueGet.first.indexKey ) || ( TEXT_FIT_FONT_SIZE_TYPE_KEY == valueGet.first.stringKey ) )
+ {
+ if( "pixelSize" == valueGet.second.Get< std::string >() )
+ {
+ type = Controller::FontSizeType::PIXEL_SIZE;
+ }
+ }
+ }
+
+ controller->SetTextFitEnabled( enabled );
+ if( isMinSizeSet )
+ {
+ controller->SetTextFitMinSize( minSize, type );
+ }
+ if( isMaxSizeSet )
+ {
+ controller->SetTextFitMaxSize( maxSize, type );
+ }
+ if( isStepSizeSet )
+ {
+ controller->SetTextFitStepSize( stepSize, type );
+ }
+ }
+}
+
} // namespace
Toolkit::TextLabel TextLabel::New()
}
case Toolkit::TextLabel::Property::AUTO_SCROLL_STOP_MODE:
{
- if( !impl.mTextScroller )
- {
- impl.mTextScroller = Text::TextScroller::New( impl );
- }
- Toolkit::TextLabel::AutoScrollStopMode::Type stopMode = impl.mTextScroller->GetStopMode();
+ Text::TextScrollerPtr textScroller = impl.GetTextScroller();
+ Toolkit::TextLabel::AutoScrollStopMode::Type stopMode = textScroller->GetStopMode();
if( Scripting::GetEnumerationProperty< Toolkit::TextLabel::AutoScrollStopMode::Type >( value,
- AUTO_SCROLL_STOP_MODE_TABLE,
- AUTO_SCROLL_STOP_MODE_TABLE_COUNT,
- stopMode ) )
+ AUTO_SCROLL_STOP_MODE_TABLE,
+ AUTO_SCROLL_STOP_MODE_TABLE_COUNT,
+ stopMode ) )
{
- impl.mTextScroller->SetStopMode( stopMode );
+ textScroller->SetStopMode( stopMode );
}
break;
}
case Toolkit::TextLabel::Property::AUTO_SCROLL_SPEED:
{
- if( !impl.mTextScroller )
- {
- impl.mTextScroller = Text::TextScroller::New( impl );
- }
- impl.mTextScroller->SetSpeed( value.Get<int>() );
+ impl.GetTextScroller()->SetSpeed( value.Get<int>() );
break;
}
case Toolkit::TextLabel::Property::AUTO_SCROLL_LOOP_COUNT:
{
- if( !impl.mTextScroller )
- {
- impl.mTextScroller = Text::TextScroller::New( impl );
- }
- impl.mTextScroller->SetLoopCount( value.Get<int>() );
+ impl.GetTextScroller()->SetLoopCount( value.Get<int>() );
break;
}
case Toolkit::TextLabel::Property::AUTO_SCROLL_LOOP_DELAY:
{
- if( !impl.mTextScroller )
- {
- impl.mTextScroller = Text::TextScroller::New( impl );
- }
- impl.mTextScroller->SetLoopDelay( value.Get<float>() );
+ impl.GetTextScroller()->SetLoopDelay( value.Get<float>() );
break;
}
case Toolkit::TextLabel::Property::AUTO_SCROLL_GAP:
{
- if( !impl.mTextScroller )
- {
- impl.mTextScroller = Text::TextScroller::New( impl );
- }
- impl.mTextScroller->SetGap( value.Get<float>() );
+ impl.GetTextScroller()->SetGap( value.Get<float>() );
break;
}
case Toolkit::TextLabel::Property::LINE_SPACING:
{
const float lineSpacing = value.Get<float>();
-
- // Don't trigger anything if the line spacing didn't change
- if( impl.mController->SetDefaultLineSpacing( lineSpacing ) )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = impl.mController->SetDefaultLineSpacing( lineSpacing ) || impl.mTextUpdateNeeded;
break;
}
case Toolkit::TextLabel::Property::UNDERLINE:
{
- const bool update = SetUnderlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
- if( update )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = SetUnderlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
break;
}
case Toolkit::TextLabel::Property::SHADOW:
{
- const bool update = SetShadowProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
- if( update )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = SetShadowProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
break;
}
case Toolkit::TextLabel::Property::EMBOSS:
{
- const bool update = SetEmbossProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
- if( update )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = SetEmbossProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
break;
}
case Toolkit::TextLabel::Property::OUTLINE:
{
- const bool update = SetOutlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
- if( update )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = SetOutlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
break;
}
case Toolkit::TextLabel::Property::PIXEL_SIZE:
}
case Toolkit::DevelTextLabel::Property::BACKGROUND:
{
- const bool update = SetBackgroundProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
- if( update )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = SetBackgroundProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
break;
}
case Toolkit::DevelTextLabel::Property::IGNORE_SPACES_AFTER_TEXT:
}
case Toolkit::DevelTextLabel::Property::TEXT_FIT:
{
- const Property::Map& propertiesMap = value.Get<Property::Map>();
-
- bool enabled = false;
- float minSize = 0.f;
- float maxSize = 0.f;
- float stepSize = 0.f;
- bool isMinSizeSet = false, isMaxSizeSet = false, isStepSizeSet = false;
- Controller::FontSizeType type = Controller::FontSizeType::POINT_SIZE;
-
- if ( !propertiesMap.Empty() )
- {
- const unsigned int numberOfItems = propertiesMap.Count();
-
- // Parses and applies
- for( unsigned int index = 0u; index < numberOfItems; ++index )
- {
- const KeyValuePair& valueGet = propertiesMap.GetKeyValue( index );
-
- if( ( Controller::TextFitInfo::Property::TEXT_FIT_ENABLE == valueGet.first.indexKey ) || ( TEXT_FIT_ENABLE_KEY == valueGet.first.stringKey ) )
- {
- /// Enable key.
- enabled = valueGet.second.Get< bool >();
- }
- else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MIN_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MIN_SIZE_KEY == valueGet.first.stringKey ) )
- {
- /// min size.
- minSize = valueGet.second.Get< float >();
- isMinSizeSet = true;
- }
- else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MAX_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MAX_SIZE_KEY == valueGet.first.stringKey ) )
- {
- /// max size.
- maxSize = valueGet.second.Get< float >();
- isMaxSizeSet = true;
- }
- else if( ( Controller::TextFitInfo::Property::TEXT_FIT_STEP_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_STEP_SIZE_KEY == valueGet.first.stringKey ) )
- {
- /// step size.
- stepSize = valueGet.second.Get< float >();
- isStepSizeSet = true;
- }
- else if( ( Controller::TextFitInfo::Property::TEXT_FIT_FONT_SIZE_TYPE == valueGet.first.indexKey ) || ( TEXT_FIT_FONT_SIZE_TYPE_KEY == valueGet.first.stringKey ) )
- {
- if( "pixelSize" == valueGet.second.Get< std::string >() )
- {
- type = Controller::FontSizeType::PIXEL_SIZE;
- }
- }
- }
-
- impl.mController->SetTextFitEnabled( enabled );
- if( isMinSizeSet )
- {
- impl.mController->SetTextFitMinSize( minSize, type );
- }
- if( isMaxSizeSet )
- {
- impl.mController->SetTextFitMaxSize( maxSize, type );
- }
- if( isStepSizeSet )
- {
- impl.mController->SetTextFitStepSize( stepSize, type );
- }
- }
+ ParseTextFitProperty(impl.mController, value.GetMap());
break;
}
case Toolkit::DevelTextLabel::Property::MIN_LINE_SIZE:
{
const float lineSize = value.Get<float>();
-
- if( impl.mController->SetDefaultLineSize( lineSize ) )
- {
- impl.mTextUpdateNeeded = true;
- }
+ impl.mTextUpdateNeeded = impl.mController->SetDefaultLineSize( lineSize ) || impl.mTextUpdateNeeded;
break;
}
}
#define DALI_TOOLKIT_INTERNAL_TEXT_LABEL_H
/*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*/
void SetUpAutoScrolling();
+ /**
+ * Creates a text-scroller if one has not been created.
+ * @return The text scroller.
+ */
+ Text::TextScrollerPtr GetTextScroller()
+ {
+ if( !mTextScroller )
+ {
+ mTextScroller = Text::TextScroller::New( *this );
+ }
+ return mTextScroller;
+ }
+
private: // Data
Text::ControllerPtr mController;
${toolkit_src_dir}/text/text-controller.cpp
${toolkit_src_dir}/text/text-controller-event-handler.cpp
${toolkit_src_dir}/text/text-controller-impl.cpp
+ ${toolkit_src_dir}/text/text-controller-impl-event-handler.cpp
${toolkit_src_dir}/text/text-controller-input-font-handler.cpp
${toolkit_src_dir}/text/text-controller-placeholder-handler.cpp
${toolkit_src_dir}/text/text-effects-style.cpp
/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
return false;
}
+/// Helper method to fetch the underline metrics for the specified font glyph
+void FetchFontUnderlineMetrics(
+ TextAbstraction::FontClient& fontClient,
+ const GlyphInfo* const glyphInfo,
+ float& currentUnderlinePosition,
+ const float underlineHeight,
+ float& currentUnderlineThickness,
+ float& maxUnderlineThickness,
+ FontId& lastUnderlinedFontId)
+{
+ FontMetrics fontMetrics;
+ fontClient.GetFontMetrics( glyphInfo->fontId, fontMetrics );
+ currentUnderlinePosition = ceil( fabsf( fontMetrics.underlinePosition ) );
+ const float descender = ceil( fabsf( fontMetrics.descender ) );
+
+ if( fabsf( underlineHeight ) < Math::MACHINE_EPSILON_1000 )
+ {
+ currentUnderlineThickness = fontMetrics.underlineThickness;
+
+ // Ensure underline will be at least a pixel high
+ if ( currentUnderlineThickness < 1.0f )
+ {
+ currentUnderlineThickness = 1.0f;
+ }
+ else
+ {
+ currentUnderlineThickness = ceil( currentUnderlineThickness );
+ }
+ }
+
+ // The underline thickness should be the max underline thickness of all glyphs of the line.
+ if ( currentUnderlineThickness > maxUnderlineThickness )
+ {
+ maxUnderlineThickness = currentUnderlineThickness;
+ }
+
+ // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
+ if( currentUnderlinePosition > descender )
+ {
+ currentUnderlinePosition = descender;
+ }
+
+ if( fabsf( currentUnderlinePosition ) < Math::MACHINE_EPSILON_1000 )
+ {
+ // Move offset down by one ( EFL behavior )
+ currentUnderlinePosition = 1.0f;
+ }
+
+ lastUnderlinedFontId = glyphInfo->fontId;
+}
+
+/// Draws the specified color to the pixel buffer
+void WriteColorToPixelBuffer(
+ GlyphData& glyphData,
+ uint32_t* bitmapBuffer,
+ const Vector4& color,
+ const unsigned int x,
+ const unsigned int y)
+{
+ // Always RGBA image for text with styles
+ uint32_t pixel = *( bitmapBuffer + y * glyphData.width + x );
+ uint8_t* pixelBuffer = reinterpret_cast<uint8_t*>( &pixel );
+
+ // Write the color to the pixel buffer
+ uint8_t colorAlpha = static_cast< uint8_t >( color.a * 255.f );
+ *( pixelBuffer + 3u ) = colorAlpha;
+ *( pixelBuffer + 2u ) = static_cast< uint8_t >( color.b * colorAlpha );
+ *( pixelBuffer + 1u ) = static_cast< uint8_t >( color.g * colorAlpha );
+ *( pixelBuffer ) = static_cast< uint8_t >( color.r * colorAlpha );
+
+ *( bitmapBuffer + y * glyphData.width + x ) = pixel;
+}
+
+/// Draws the specified underline color to the buffer
+void DrawUnderline(
+ const Vector4& underlineColor,
+ const unsigned int bufferWidth,
+ const unsigned int bufferHeight,
+ GlyphData& glyphData,
+ const float baseline,
+ const float currentUnderlinePosition,
+ const float maxUnderlineThickness,
+ const float lineExtentLeft,
+ const float lineExtentRight)
+{
+ int underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
+ uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
+
+ for( unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++ )
+ {
+ if( y > bufferHeight - 1 )
+ {
+ // Do not write out of bounds.
+ break;
+ }
+
+ for( unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
+ {
+ if( x > bufferWidth - 1 )
+ {
+ // Do not write out of bounds.
+ break;
+ }
+
+ WriteColorToPixelBuffer(glyphData, bitmapBuffer, underlineColor, x, y);
+ }
+ }
+}
+
+/// Draws the background color to the buffer
+void DrawBackgroundColor(
+ Vector4 backgroundColor,
+ const unsigned int bufferWidth,
+ const unsigned int bufferHeight,
+ GlyphData& glyphData,
+ const float baseline,
+ const LineRun& line,
+ const float lineExtentLeft,
+ const float lineExtentRight)
+{
+ uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
+
+ for( int y = glyphData.verticalOffset + baseline - line.ascender; y < glyphData.verticalOffset + baseline - line.descender; y++ )
+ {
+ if( ( y < 0 ) || ( y > static_cast<int>(bufferHeight - 1) ) )
+ {
+ // Do not write out of bounds.
+ continue;
+ }
+
+ for( int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
+ {
+ if( ( x < 0 ) || ( x > static_cast<int>(bufferWidth - 1) ) )
+ {
+ // Do not write out of bounds.
+ continue;
+ }
+
+ WriteColorToPixelBuffer(glyphData, bitmapBuffer, backgroundColor, x, y);
+ }
+ }
+}
+
} // namespace
TypesetterPtr Typesetter::New( const ModelInterface* const model )
if( underlineGlyph && ( glyphInfo->fontId != lastUnderlinedFontId ) )
{
// We need to fetch fresh font underline metrics
- FontMetrics fontMetrics;
- fontClient.GetFontMetrics( glyphInfo->fontId, fontMetrics );
- currentUnderlinePosition = ceil( fabsf( fontMetrics.underlinePosition ) );
- const float descender = ceil( fabsf( fontMetrics.descender ) );
-
- if( fabsf( underlineHeight ) < Math::MACHINE_EPSILON_1000 )
- {
- currentUnderlineThickness = fontMetrics.underlineThickness;
-
- // Ensure underline will be at least a pixel high
- if ( currentUnderlineThickness < 1.0f )
- {
- currentUnderlineThickness = 1.0f;
- }
- else
- {
- currentUnderlineThickness = ceil( currentUnderlineThickness );
- }
- }
-
- // The underline thickness should be the max underline thickness of all glyphs of the line.
- if ( currentUnderlineThickness > maxUnderlineThickness )
- {
- maxUnderlineThickness = currentUnderlineThickness;
- }
-
- // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
- if( currentUnderlinePosition > descender )
- {
- currentUnderlinePosition = descender;
- }
-
- if( fabsf( currentUnderlinePosition ) < Math::MACHINE_EPSILON_1000 )
- {
- // Move offset down by one ( EFL behavior )
- currentUnderlinePosition = 1.0f;
- }
-
- lastUnderlinedFontId = glyphInfo->fontId;
+ FetchFontUnderlineMetrics(fontClient, glyphInfo, currentUnderlinePosition, underlineHeight, currentUnderlineThickness, maxUnderlineThickness, lastUnderlinedFontId);
} // underline
// Retrieves the glyph's position.
// Draw the underline from the leftmost glyph to the rightmost glyph
if ( thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE )
{
- int underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
-
- for( unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++ )
- {
- if( y > bufferHeight - 1 )
- {
- // Do not write out of bounds.
- break;
- }
-
- for( unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
- {
- if( x > bufferWidth - 1 )
- {
- // Do not write out of bounds.
- break;
- }
-
- // Always RGBA image for text with styles
- uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
- uint32_t underlinePixel = *( bitmapBuffer + y * glyphData.width + x );
- uint8_t* underlinePixelBuffer = reinterpret_cast<uint8_t*>( &underlinePixel );
-
- // Write the underline color to the pixel buffer
- uint8_t colorAlpha = static_cast< uint8_t >( underlineColor.a * 255.f );
- *( underlinePixelBuffer + 3u ) = colorAlpha;
- *( underlinePixelBuffer + 2u ) = static_cast< uint8_t >( underlineColor.b * colorAlpha );
- *( underlinePixelBuffer + 1u ) = static_cast< uint8_t >( underlineColor.g * colorAlpha );
- *( underlinePixelBuffer ) = static_cast< uint8_t >( underlineColor.r * colorAlpha );
-
- *( bitmapBuffer + y * glyphData.width + x ) = underlinePixel;
- }
- }
+ DrawUnderline(underlineColor, bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineThickness, lineExtentLeft, lineExtentRight);
}
// Draw the background color from the leftmost glyph to the rightmost glyph
if ( style == Typesetter::STYLE_BACKGROUND )
{
- Vector4 backgroundColor = mModel->GetBackgroundColor();
-
- for( int y = glyphData.verticalOffset + baseline - line.ascender; y < glyphData.verticalOffset + baseline - line.descender; y++ )
- {
- if( ( y < 0 ) || ( y > static_cast<int>(bufferHeight - 1) ) )
- {
- // Do not write out of bounds.
- continue;
- }
-
- for( int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
- {
- if( ( x < 0 ) || ( x > static_cast<int>(bufferWidth - 1) ) )
- {
- // Do not write out of bounds.
- continue;
- }
-
- // Always RGBA image for text with styles
- uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
- uint32_t backgroundPixel = *( bitmapBuffer + y * glyphData.width + x );
- uint8_t* backgroundPixelBuffer = reinterpret_cast<uint8_t*>( &backgroundPixel );
-
- // Write the background color to the pixel buffer
- uint8_t colorAlpha = static_cast< uint8_t >( backgroundColor.a * 255.f );
- *( backgroundPixelBuffer + 3u ) = colorAlpha;
- *( backgroundPixelBuffer + 2u ) = static_cast< uint8_t >( backgroundColor.b * colorAlpha );
- *( backgroundPixelBuffer + 1u ) = static_cast< uint8_t >( backgroundColor.g * colorAlpha );
- *( backgroundPixelBuffer ) = static_cast< uint8_t >( backgroundColor.r * colorAlpha );
-
- *( bitmapBuffer + y * glyphData.width + x ) = backgroundPixel;
- }
- }
+ DrawBackgroundColor(mModel->GetBackgroundColor(), bufferWidth, bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight);
}
// Increases the vertical offset with the line's descender.
--- /dev/null
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/adaptor-framework/key.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/cursor-helper-functions.h>
+
+using namespace Dali;
+
+namespace
+{
+
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+#endif
+
+} // namespace
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+void ControllerImplEventHandler::OnCursorKeyEvent(Controller::Impl& impl, const Event& event)
+{
+ if( NULL == impl.mEventData || !impl.IsShowingRealText() )
+ {
+ // Nothing to do if there is no text input.
+ return;
+ }
+
+ int keyCode = event.p1.mInt;
+ bool isShiftModifier = event.p2.mBool;
+ EventData& eventData = *impl.mEventData;
+ ModelPtr& model = impl.mModel;
+ LogicalModelPtr& logicalModel = model->mLogicalModel;
+ VisualModelPtr& visualModel = model->mVisualModel;
+
+ CharacterIndex& primaryCursorPosition = eventData.mPrimaryCursorPosition;
+ CharacterIndex previousPrimaryCursorPosition = primaryCursorPosition;
+
+ if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
+ {
+ if( primaryCursorPosition > 0u )
+ {
+ if ( !isShiftModifier && eventData.mDecorator->IsHighlightVisible() )
+ {
+ primaryCursorPosition = std::min(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
+ }
+ else
+ {
+ primaryCursorPosition = impl.CalculateNewCursorIndex( primaryCursorPosition - 1u );
+ }
+ }
+ }
+ else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
+ {
+ if( logicalModel->mText.Count() > primaryCursorPosition )
+ {
+ if ( !isShiftModifier && eventData.mDecorator->IsHighlightVisible() )
+ {
+ primaryCursorPosition = std::max(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
+ }
+ else
+ {
+ primaryCursorPosition = impl.CalculateNewCursorIndex( primaryCursorPosition );
+ }
+ }
+ }
+ else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
+ {
+ // Ignore Shift-Up for text selection for now.
+
+ // Get first the line index of the current cursor position index.
+ CharacterIndex characterIndex = 0u;
+
+ if( primaryCursorPosition > 0u )
+ {
+ characterIndex = primaryCursorPosition - 1u;
+ }
+
+ const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
+ const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
+
+ // Retrieve the cursor position info.
+ CursorInfo cursorInfo;
+ impl.GetCursorPosition( primaryCursorPosition,
+ cursorInfo );
+
+ // Get the line above.
+ const LineRun& line = *( visualModel->mLines.Begin() + previousLineIndex );
+
+ // Get the next hit 'y' point.
+ const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
+
+ // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+ bool matchedCharacter = false;
+ primaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
+ logicalModel,
+ impl.mMetrics,
+ eventData.mCursorHookPositionX,
+ hitPointY,
+ CharacterHitTest::TAP,
+ matchedCharacter );
+ }
+ else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
+ {
+ // Ignore Shift-Down for text selection for now.
+
+ // Get first the line index of the current cursor position index.
+ CharacterIndex characterIndex = 0u;
+
+ if( primaryCursorPosition > 0u )
+ {
+ characterIndex = primaryCursorPosition - 1u;
+ }
+
+ const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
+
+ if( lineIndex + 1u < visualModel->mLines.Count() )
+ {
+ // Retrieve the cursor position info.
+ CursorInfo cursorInfo;
+ impl.GetCursorPosition( primaryCursorPosition, cursorInfo );
+
+ // Get the line below.
+ const LineRun& line = *( visualModel->mLines.Begin() + lineIndex + 1u );
+
+ // Get the next hit 'y' point.
+ const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
+
+ // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+ bool matchedCharacter = false;
+ primaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
+ logicalModel,
+ impl.mMetrics,
+ eventData.mCursorHookPositionX,
+ hitPointY,
+ CharacterHitTest::TAP,
+ matchedCharacter );
+ }
+ }
+
+ if ( !isShiftModifier && eventData.mState != EventData::SELECTING )
+ {
+ // Update selection position after moving the cursor
+ eventData.mLeftSelectionPosition = primaryCursorPosition;
+ eventData.mRightSelectionPosition = primaryCursorPosition;
+ }
+
+ if ( isShiftModifier && impl.IsShowingRealText() && eventData.mShiftSelectionFlag )
+ {
+ // Handle text selection
+ bool selecting = false;
+
+ if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
+ {
+ // Shift-Left/Right to select the text
+ int cursorPositionDelta = primaryCursorPosition - previousPrimaryCursorPosition;
+ if ( cursorPositionDelta > 0 || eventData.mRightSelectionPosition > 0u ) // Check the boundary
+ {
+ eventData.mRightSelectionPosition += cursorPositionDelta;
+ }
+ selecting = true;
+ }
+ else if ( eventData.mLeftSelectionPosition != eventData.mRightSelectionPosition )
+ {
+ // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
+ selecting = true;
+ }
+
+ if ( selecting )
+ {
+ // Notify the cursor position to the InputMethodContext.
+ if( eventData.mInputMethodContext )
+ {
+ eventData.mInputMethodContext.SetCursorPosition( primaryCursorPosition );
+ eventData.mInputMethodContext.NotifyCursorPosition();
+ }
+
+ impl.ChangeState( EventData::SELECTING );
+
+ eventData.mUpdateLeftSelectionPosition = true;
+ eventData.mUpdateRightSelectionPosition = true;
+ eventData.mUpdateGrabHandlePosition = true;
+ eventData.mUpdateHighlightBox = true;
+
+ // Hide the text selection popup if select the text using keyboard instead of moving grab handles
+ if( eventData.mGrabHandlePopupEnabled )
+ {
+ eventData.mDecorator->SetPopupActive( false );
+ }
+ }
+ }
+ else
+ {
+ // Handle normal cursor move
+ impl.ChangeState( EventData::EDITING );
+ eventData.mUpdateCursorPosition = true;
+ }
+
+ eventData.mUpdateInputStyle = true;
+ eventData.mScrollAfterUpdatePosition = true;
+}
+
+void ControllerImplEventHandler::OnTapEvent(Controller::Impl& impl, const Event& event)
+{
+ if( impl.mEventData )
+ {
+ const unsigned int tapCount = event.p1.mUint;
+ EventData& eventData = *impl.mEventData;
+ ModelPtr& model = impl.mModel;
+ LogicalModelPtr& logicalModel = model->mLogicalModel;
+ VisualModelPtr& visualModel = model->mVisualModel;
+
+ if( 1u == tapCount )
+ {
+ if( impl.IsShowingRealText() )
+ {
+ // Convert from control's coords to text's coords.
+ const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
+
+ // Keep the tap 'x' position. Used to move the cursor.
+ eventData.mCursorHookPositionX = xPosition;
+
+ // Whether to touch point hits on a glyph.
+ bool matchedCharacter = false;
+ eventData.mPrimaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
+ logicalModel,
+ impl.mMetrics,
+ xPosition,
+ yPosition,
+ CharacterHitTest::TAP,
+ matchedCharacter );
+
+ // When the cursor position is changing, delay cursor blinking
+ eventData.mDecorator->DelayCursorBlink();
+ }
+ else
+ {
+ eventData.mPrimaryCursorPosition = 0u;
+ }
+
+ // Update selection position after tapping
+ eventData.mLeftSelectionPosition = eventData.mPrimaryCursorPosition;
+ eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
+
+ eventData.mUpdateCursorPosition = true;
+ eventData.mUpdateGrabHandlePosition = true;
+ eventData.mScrollAfterUpdatePosition = true;
+ eventData.mUpdateInputStyle = true;
+
+ // Notify the cursor position to the InputMethodContext.
+ if( eventData.mInputMethodContext )
+ {
+ eventData.mInputMethodContext.SetCursorPosition( eventData.mPrimaryCursorPosition );
+ eventData.mInputMethodContext.NotifyCursorPosition();
+ }
+ }
+ else if( 2u == tapCount )
+ {
+ if( eventData.mSelectionEnabled )
+ {
+ // Convert from control's coords to text's coords.
+ const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
+
+ // Calculates the logical position from the x,y coords.
+ impl.RepositionSelectionHandles( xPosition, yPosition, eventData.mDoubleTapAction );
+ }
+ }
+ }
+}
+
+void ControllerImplEventHandler::OnPanEvent(Controller::Impl& impl, const Event& event)
+{
+ if( impl.mEventData )
+ {
+ EventData& eventData = *impl.mEventData;
+ DecoratorPtr& decorator = eventData.mDecorator;
+
+ const bool isHorizontalScrollEnabled = decorator->IsHorizontalScrollEnabled();
+ const bool isVerticalScrollEnabled = decorator->IsVerticalScrollEnabled();
+
+ if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
+ {
+ // Nothing to do if scrolling is not enabled.
+ return;
+ }
+
+ const GestureState state = static_cast<GestureState>( event.p1.mInt );
+ switch( state )
+ {
+ case GestureState::STARTED:
+ {
+ // Will remove the cursor, handles or text's popup, ...
+ impl.ChangeState( EventData::TEXT_PANNING );
+ break;
+ }
+ case GestureState::CONTINUING:
+ {
+ ModelPtr& model = impl.mModel;
+
+ const Vector2& layoutSize = model->mVisualModel->GetLayoutSize();
+ Vector2& scrollPosition = model->mScrollPosition;
+ const Vector2 currentScroll = scrollPosition;
+
+ if( isHorizontalScrollEnabled )
+ {
+ const float displacementX = event.p2.mFloat;
+ scrollPosition.x += displacementX;
+
+ impl.ClampHorizontalScroll( layoutSize );
+ }
+
+ if( isVerticalScrollEnabled )
+ {
+ const float displacementY = event.p3.mFloat;
+ scrollPosition.y += displacementY;
+
+ impl.ClampVerticalScroll( layoutSize );
+ }
+
+ decorator->UpdatePositions( scrollPosition - currentScroll );
+ break;
+ }
+ case GestureState::FINISHED:
+ case GestureState::CANCELLED: // FALLTHROUGH
+ {
+ // Will go back to the previous state to show the cursor, handles, the text's popup, ...
+ impl.ChangeState( eventData.mPreviousState );
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+void ControllerImplEventHandler::OnLongPressEvent(Controller::Impl& impl, const Event& event)
+{
+ DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
+
+ if( impl.mEventData )
+ {
+ EventData& eventData = *impl.mEventData;
+
+ if( !impl.IsShowingRealText() && ( EventData::EDITING == eventData.mState ) )
+ {
+ impl.ChangeState( EventData::EDITING_WITH_POPUP );
+ eventData.mDecoratorUpdated = true;
+ eventData.mUpdateInputStyle = true;
+ }
+ else
+ {
+ if( eventData.mSelectionEnabled )
+ {
+ ModelPtr& model = impl.mModel;
+
+ // Convert from control's coords to text's coords.
+ const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
+ const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
+
+ // Calculates the logical position from the x,y coords.
+ impl.RepositionSelectionHandles( xPosition, yPosition, eventData.mLongPressAction );
+ }
+ }
+ }
+}
+
+void ControllerImplEventHandler::OnHandleEvent(Controller::Impl& impl, const Event& event)
+{
+ if( impl.mEventData )
+ {
+ const unsigned int state = event.p1.mUint;
+ const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
+ const bool isSmoothHandlePanEnabled = impl.mEventData->mDecorator->IsSmoothHandlePanEnabled();
+
+ if( HANDLE_PRESSED == state )
+ {
+ OnHandlePressed(impl, event, isSmoothHandlePanEnabled);
+ } // end ( HANDLE_PRESSED == state )
+ else if( ( HANDLE_RELEASED == state ) ||
+ handleStopScrolling )
+ {
+ OnHandleReleased(impl, event, isSmoothHandlePanEnabled, handleStopScrolling);
+ } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
+ else if( HANDLE_SCROLLING == state )
+ {
+ OnHandleScrolling(impl, event, isSmoothHandlePanEnabled);
+ } // end ( HANDLE_SCROLLING == state )
+ }
+}
+
+void ControllerImplEventHandler::OnSelectEvent(Controller::Impl& impl, const Event& event )
+{
+ if( impl.mEventData && impl.mEventData->mSelectionEnabled )
+ {
+ ModelPtr& model = impl.mModel;
+ const Vector2& scrollPosition = model->mScrollPosition;
+
+ // Convert from control's coords to text's coords.
+ const float xPosition = event.p2.mFloat - scrollPosition.x;
+ const float yPosition = event.p3.mFloat - scrollPosition.y;
+
+ // Calculates the logical position from the x,y coords.
+ impl.RepositionSelectionHandles( xPosition, yPosition, Controller::NoTextTap::HIGHLIGHT );
+ }
+}
+
+void ControllerImplEventHandler::OnSelectAllEvent(Controller::Impl& impl)
+{
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled?"true":"false");
+
+ if( impl.mEventData )
+ {
+ EventData& eventData = *impl.mEventData;
+ if( eventData.mSelectionEnabled )
+ {
+ ModelPtr& model = impl.mModel;
+ const Vector2& scrollPosition = model->mScrollPosition;
+
+ // Calculates the logical position from the start.
+ impl.RepositionSelectionHandles( 0.f - scrollPosition.x,
+ 0.f - scrollPosition.y,
+ Controller::NoTextTap::HIGHLIGHT );
+
+ eventData.mLeftSelectionPosition = 0u;
+ eventData.mRightSelectionPosition = model->mLogicalModel->mText.Count();
+ }
+ }
+}
+
+void ControllerImplEventHandler::OnSelectNoneEvent(Controller::Impl& impl)
+{
+ DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled?"true":"false");
+
+ if( impl.mEventData )
+ {
+ EventData& eventData = *impl.mEventData;
+ if( eventData.mSelectionEnabled && eventData.mState == EventData::SELECTING)
+ {
+ eventData.mPrimaryCursorPosition = 0u;
+ eventData.mLeftSelectionPosition = eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
+ impl.ChangeState( EventData::INACTIVE );
+ eventData.mUpdateCursorPosition = true;
+ eventData.mUpdateInputStyle = true;
+ eventData.mScrollAfterUpdatePosition = true;
+ }
+ }
+}
+
+void ControllerImplEventHandler::OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
+{
+ ModelPtr& model = impl.mModel;
+ const Vector2& scrollPosition = model->mScrollPosition;
+
+ // Convert from decorator's coords to text's coords.
+ const float xPosition = event.p2.mFloat - scrollPosition.x;
+ const float yPosition = event.p3.mFloat - scrollPosition.y;
+
+ // Need to calculate the handle's new position.
+ bool matchedCharacter = false;
+ const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( model->mVisualModel,
+ model->mLogicalModel,
+ impl.mMetrics,
+ xPosition,
+ yPosition,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
+
+ EventData& eventData = *impl.mEventData;
+
+ if( Event::GRAB_HANDLE_EVENT == event.type )
+ {
+ impl.ChangeState ( EventData::GRAB_HANDLE_PANNING );
+
+ if( handleNewPosition != eventData.mPrimaryCursorPosition )
+ {
+ // Updates the cursor position if the handle's new position is different than the current one.
+ eventData.mUpdateCursorPosition = true;
+ // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
+ eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
+ eventData.mPrimaryCursorPosition = handleNewPosition;
+ }
+
+ // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
+ eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+ }
+ else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
+ {
+ impl.ChangeState ( EventData::SELECTION_HANDLE_PANNING );
+
+ if( ( handleNewPosition != eventData.mLeftSelectionPosition ) &&
+ ( handleNewPosition != eventData.mRightSelectionPosition ) )
+ {
+ // Updates the highlight box if the handle's new position is different than the current one.
+ eventData.mUpdateHighlightBox = true;
+ // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
+ eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
+ eventData.mLeftSelectionPosition = handleNewPosition;
+ }
+
+ // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
+ eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+
+ // Will define the order to scroll the text to match the handle position.
+ eventData.mIsLeftHandleSelected = true;
+ eventData.mIsRightHandleSelected = false;
+ }
+ else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+ {
+ impl.ChangeState ( EventData::SELECTION_HANDLE_PANNING );
+
+ if( ( handleNewPosition != eventData.mRightSelectionPosition ) &&
+ ( handleNewPosition != eventData.mLeftSelectionPosition ) )
+ {
+ // Updates the highlight box if the handle's new position is different than the current one.
+ eventData.mUpdateHighlightBox = true;
+ // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
+ eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
+ eventData.mRightSelectionPosition = handleNewPosition;
+ }
+
+ // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
+ eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+
+ // Will define the order to scroll the text to match the handle position.
+ eventData.mIsLeftHandleSelected = false;
+ eventData.mIsRightHandleSelected = true;
+ }
+}
+
+void ControllerImplEventHandler::OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling)
+{
+ CharacterIndex handlePosition = 0u;
+ if( handleStopScrolling || isSmoothHandlePanEnabled )
+ {
+ ModelPtr& model = impl.mModel;
+ const Vector2& scrollPosition = model->mScrollPosition;
+
+ // Convert from decorator's coords to text's coords.
+ const float xPosition = event.p2.mFloat - scrollPosition.x;
+ const float yPosition = event.p3.mFloat - scrollPosition.y;
+
+ bool matchedCharacter = false;
+ handlePosition = Text::GetClosestCursorIndex( model->mVisualModel,
+ model->mLogicalModel,
+ impl.mMetrics,
+ xPosition,
+ yPosition,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
+ }
+
+ EventData& eventData = *impl.mEventData;
+
+ if( Event::GRAB_HANDLE_EVENT == event.type )
+ {
+ eventData.mUpdateCursorPosition = true;
+ eventData.mUpdateGrabHandlePosition = true;
+ eventData.mUpdateInputStyle = true;
+
+ if( !impl.IsClipboardEmpty() )
+ {
+ impl.ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
+ }
+
+ if( handleStopScrolling || isSmoothHandlePanEnabled )
+ {
+ eventData.mScrollAfterUpdatePosition = true;
+ eventData.mPrimaryCursorPosition = handlePosition;
+ }
+ }
+ else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
+ {
+ impl.ChangeState( EventData::SELECTING );
+
+ eventData.mUpdateHighlightBox = true;
+ eventData.mUpdateLeftSelectionPosition = true;
+ eventData.mUpdateRightSelectionPosition = true;
+
+ if( handleStopScrolling || isSmoothHandlePanEnabled )
+ {
+ eventData.mScrollAfterUpdatePosition = true;
+
+ if( ( handlePosition != eventData.mRightSelectionPosition ) &&
+ ( handlePosition != eventData.mLeftSelectionPosition ) )
+ {
+ eventData.mLeftSelectionPosition = handlePosition;
+ }
+ }
+ }
+ else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+ {
+ impl.ChangeState( EventData::SELECTING );
+
+ eventData.mUpdateHighlightBox = true;
+ eventData.mUpdateRightSelectionPosition = true;
+ eventData.mUpdateLeftSelectionPosition = true;
+
+ if( handleStopScrolling || isSmoothHandlePanEnabled )
+ {
+ eventData.mScrollAfterUpdatePosition = true;
+ if( ( handlePosition != eventData.mRightSelectionPosition ) &&
+ ( handlePosition != eventData.mLeftSelectionPosition ) )
+ {
+ eventData.mRightSelectionPosition = handlePosition;
+ }
+ }
+ }
+
+ eventData.mDecoratorUpdated = true;
+}
+
+void ControllerImplEventHandler::OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
+{
+ ModelPtr& model = impl.mModel;
+ Vector2& scrollPosition = model->mScrollPosition;
+ VisualModelPtr& visualModel = model->mVisualModel;
+
+ const float xSpeed = event.p2.mFloat;
+ const float ySpeed = event.p3.mFloat;
+ const Vector2& layoutSize = visualModel->GetLayoutSize();
+ const Vector2 currentScrollPosition = scrollPosition;
+
+ scrollPosition.x += xSpeed;
+ scrollPosition.y += ySpeed;
+
+ impl.ClampHorizontalScroll( layoutSize );
+ impl.ClampVerticalScroll( layoutSize );
+
+ EventData& eventData = *impl.mEventData;
+ DecoratorPtr& decorator = eventData.mDecorator;
+
+ bool endOfScroll = false;
+ if( Vector2::ZERO == ( currentScrollPosition - scrollPosition ) )
+ {
+ // Notify the decorator there is no more text to scroll.
+ // The decorator won't send more scroll events.
+ decorator->NotifyEndOfScroll();
+ // Still need to set the position of the handle.
+ endOfScroll = true;
+ }
+
+ // Set the position of the handle.
+ const bool scrollRightDirection = xSpeed > 0.f;
+ const bool scrollBottomDirection = ySpeed > 0.f;
+ const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
+ const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
+
+ if( Event::GRAB_HANDLE_EVENT == event.type )
+ {
+ impl.ChangeState( EventData::GRAB_HANDLE_PANNING );
+
+ // Get the grab handle position in decorator coords.
+ Vector2 position = decorator->GetPosition( GRAB_HANDLE );
+
+ if( decorator->IsHorizontalScrollEnabled() )
+ {
+ // Position the grag handle close to either the left or right edge.
+ position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
+ }
+
+ if( decorator->IsVerticalScrollEnabled() )
+ {
+ position.x = eventData.mCursorHookPositionX;
+
+ // Position the grag handle close to either the top or bottom edge.
+ position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
+ }
+
+ // Get the new handle position.
+ // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
+ bool matchedCharacter = false;
+ const CharacterIndex handlePosition = Text::GetClosestCursorIndex( visualModel,
+ impl.mModel->mLogicalModel,
+ impl.mMetrics,
+ position.x - scrollPosition.x,
+ position.y - scrollPosition.y,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
+
+ if( eventData.mPrimaryCursorPosition != handlePosition )
+ {
+ eventData.mUpdateCursorPosition = true;
+ eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
+ eventData.mScrollAfterUpdatePosition = true;
+ eventData.mPrimaryCursorPosition = handlePosition;
+ }
+ eventData.mUpdateInputStyle = eventData.mUpdateCursorPosition;
+
+ // Updates the decorator if the soft handle panning is enabled.
+ eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+ }
+ else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
+ {
+ impl.ChangeState( EventData::SELECTION_HANDLE_PANNING );
+
+ // Get the selection handle position in decorator coords.
+ Vector2 position = decorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
+
+ if( decorator->IsHorizontalScrollEnabled() )
+ {
+ // Position the selection handle close to either the left or right edge.
+ position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
+ }
+
+ if( decorator->IsVerticalScrollEnabled() )
+ {
+ position.x = eventData.mCursorHookPositionX;
+
+ // Position the grag handle close to either the top or bottom edge.
+ position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
+ }
+
+ // Get the new handle position.
+ // The selection handle's position is in decorator's coords. Need to transform to text's coords.
+ bool matchedCharacter = false;
+ const CharacterIndex handlePosition = Text::GetClosestCursorIndex( visualModel,
+ impl.mModel->mLogicalModel,
+ impl.mMetrics,
+ position.x - scrollPosition.x,
+ position.y - scrollPosition.y,
+ CharacterHitTest::SCROLL,
+ matchedCharacter );
+
+ if( leftSelectionHandleEvent )
+ {
+ const bool differentHandles = ( eventData.mLeftSelectionPosition != handlePosition ) && ( eventData.mRightSelectionPosition != handlePosition );
+
+ if( differentHandles || endOfScroll )
+ {
+ eventData.mUpdateHighlightBox = true;
+ eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
+ eventData.mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
+ eventData.mLeftSelectionPosition = handlePosition;
+ }
+ }
+ else
+ {
+ const bool differentHandles = ( eventData.mRightSelectionPosition != handlePosition ) && ( eventData.mLeftSelectionPosition != handlePosition );
+ if( differentHandles || endOfScroll )
+ {
+ eventData.mUpdateHighlightBox = true;
+ eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
+ eventData.mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
+ eventData.mRightSelectionPosition = handlePosition;
+ }
+ }
+
+ if( eventData.mUpdateLeftSelectionPosition || eventData.mUpdateRightSelectionPosition )
+ {
+ impl.RepositionSelectionHandles();
+
+ eventData.mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
+ }
+ }
+ eventData.mDecoratorUpdated = true;
+}
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
--- /dev/null
+#ifndef DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_EVENT_HANDLER_H
+#define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_EVENT_HANDLER_H
+
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-controller-impl.h>
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+/**
+ * Contains all the event handling methods for Text::Controller::Impl
+ */
+struct ControllerImplEventHandler
+{
+ /**
+ * @brief Called by Controller::Impl when a cursor key event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnCursorKeyEvent(Controller::Impl& controllerImpl, const Event& event);
+
+ /**
+ * @brief Called by Controller::Impl when a tap event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnTapEvent(Controller::Impl& controllerImpl, const Event& event);
+
+ /**
+ * @brief Called by Controller::Impl when a pan event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnPanEvent(Controller::Impl& controllerImpl, const Event& event);
+
+ /**
+ * @brief Called by Controller::Impl when a long press event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnLongPressEvent(Controller::Impl& controllerImpl, const Event& event);
+
+ /**
+ * @brief Called by Controller::Impl when a handle event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnHandleEvent(Controller::Impl& controllerImpl, const Event& event);
+
+ /**
+ * @brief Called by Controller::Impl when a select event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnSelectEvent(Controller::Impl& controllerImpl, const Event& event );
+
+ /**
+ * @brief Called by Controller::Impl when a select all event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnSelectAllEvent(Controller::Impl& controllerImpl);
+
+ /**
+ * @brief Called by Controller::Impl when a select none event is received.
+ *
+ * @param controllerImpl A reference to Controller::Impl
+ * @param event The event
+ */
+ static void OnSelectNoneEvent(Controller::Impl& controllerImpl);
+
+private:
+
+ /**
+ * @brief Called by OnHandleEvent when we are in the Pressed state.
+ *
+ * @param impl A reference to Controller::Impl
+ * @param event The event
+ * @param isSmoothHandlePanEnabled Whether smooth handle pan is enabled
+ */
+ static void OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled);
+
+ /**
+ * @brief Called by OnHandleEvent when we are in the Released state.
+ *
+ * @param impl A reference to Controller::Impl
+ * @param event The event
+ * @param isSmoothHandlePanEnabled Whether smooth handle pan is enabled
+ * @param handleStopScrolling Whether we should handle stop scrolling or not
+ */
+ static void OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling);
+
+ /**
+ * @brief Called by OnHandleEvent when we are in the Scrolling state.
+ *
+ * @param impl A reference to Controller::Impl
+ * @param event The event
+ * @param isSmoothHandlePanEnabled Whether smooth handle pan is enabled
+ */
+ static void OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled);
+};
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_EVENT_HANDLER_H
/*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali-toolkit/internal/text/text-controller-impl.h>
// EXTERNAL INCLUDES
-#include <dali/public-api/adaptor-framework/key.h>
#include <dali/public-api/rendering/renderer.h>
#include <dali/integration-api/debug.h>
#include <limits>
#include <dali-toolkit/internal/text/segmentation.h>
#include <dali-toolkit/internal/text/shaper.h>
#include <dali-toolkit/internal/text/text-control-interface.h>
+#include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
#include <dali-toolkit/internal/text/text-run-container.h>
using namespace Dali;
};
#if defined(DEBUG_ENABLED)
- Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
#endif
const float MAX_FLOAT = std::numeric_limits<float>::max();
void Controller::Impl::OnCursorKeyEvent( const Event& event )
{
- if( NULL == mEventData || !IsShowingRealText() )
- {
- // Nothing to do if there is no text input.
- return;
- }
-
- int keyCode = event.p1.mInt;
- bool isShiftModifier = event.p2.mBool;
-
- CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
-
- if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
- {
- if( mEventData->mPrimaryCursorPosition > 0u )
- {
- if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
- {
- mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
- }
- else
- {
- mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
- }
- }
- }
- else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
- {
- if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
- {
- if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
- {
- mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
- }
- else
- {
- mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
- }
- }
- }
- else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
- {
- // Ignore Shift-Up for text selection for now.
-
- // Get first the line index of the current cursor position index.
- CharacterIndex characterIndex = 0u;
-
- if( mEventData->mPrimaryCursorPosition > 0u )
- {
- characterIndex = mEventData->mPrimaryCursorPosition - 1u;
- }
-
- const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
- const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
-
- // Retrieve the cursor position info.
- CursorInfo cursorInfo;
- GetCursorPosition( mEventData->mPrimaryCursorPosition,
- cursorInfo );
-
- // Get the line above.
- const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
-
- // Get the next hit 'y' point.
- const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
-
- // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
- bool matchedCharacter = false;
- mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- mEventData->mCursorHookPositionX,
- hitPointY,
- CharacterHitTest::TAP,
- matchedCharacter );
- }
- else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
- {
- // Ignore Shift-Down for text selection for now.
-
- // Get first the line index of the current cursor position index.
- CharacterIndex characterIndex = 0u;
-
- if( mEventData->mPrimaryCursorPosition > 0u )
- {
- characterIndex = mEventData->mPrimaryCursorPosition - 1u;
- }
-
- const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
-
- if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
- {
- // Retrieve the cursor position info.
- CursorInfo cursorInfo;
- GetCursorPosition( mEventData->mPrimaryCursorPosition,
- cursorInfo );
-
- // Get the line below.
- const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
-
- // Get the next hit 'y' point.
- const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
-
- // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
- bool matchedCharacter = false;
- mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- mEventData->mCursorHookPositionX,
- hitPointY,
- CharacterHitTest::TAP,
- matchedCharacter );
- }
- }
-
- if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
- {
- // Update selection position after moving the cursor
- mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
- mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
- }
-
- if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
- {
- // Handle text selection
- bool selecting = false;
-
- if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
- {
- // Shift-Left/Right to select the text
- int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
- if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
- {
- mEventData->mRightSelectionPosition += cursorPositionDelta;
- }
- selecting = true;
- }
- else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
- {
- // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
- selecting = true;
- }
-
- if ( selecting )
- {
- // Notify the cursor position to the InputMethodContext.
- if( mEventData->mInputMethodContext )
- {
- mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
- mEventData->mInputMethodContext.NotifyCursorPosition();
- }
-
- ChangeState( EventData::SELECTING );
-
- mEventData->mUpdateLeftSelectionPosition = true;
- mEventData->mUpdateRightSelectionPosition = true;
- mEventData->mUpdateGrabHandlePosition = true;
- mEventData->mUpdateHighlightBox = true;
-
- // Hide the text selection popup if select the text using keyboard instead of moving grab handles
- if( mEventData->mGrabHandlePopupEnabled )
- {
- mEventData->mDecorator->SetPopupActive( false );
- }
- }
- }
- else
- {
- // Handle normal cursor move
- ChangeState( EventData::EDITING );
- mEventData->mUpdateCursorPosition = true;
- }
-
- mEventData->mUpdateInputStyle = true;
- mEventData->mScrollAfterUpdatePosition = true;
+ ControllerImplEventHandler::OnCursorKeyEvent(*this, event);
}
void Controller::Impl::OnTapEvent( const Event& event )
{
- if( NULL != mEventData )
- {
- const unsigned int tapCount = event.p1.mUint;
-
- if( 1u == tapCount )
- {
- if( IsShowingRealText() )
- {
- // Convert from control's coords to text's coords.
- const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
- const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
- // Keep the tap 'x' position. Used to move the cursor.
- mEventData->mCursorHookPositionX = xPosition;
-
- // Whether to touch point hits on a glyph.
- bool matchedCharacter = false;
- mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- xPosition,
- yPosition,
- CharacterHitTest::TAP,
- matchedCharacter );
-
- // When the cursor position is changing, delay cursor blinking
- mEventData->mDecorator->DelayCursorBlink();
- }
- else
- {
- mEventData->mPrimaryCursorPosition = 0u;
- }
-
- // Update selection position after tapping
- mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
- mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
-
- mEventData->mUpdateCursorPosition = true;
- mEventData->mUpdateGrabHandlePosition = true;
- mEventData->mScrollAfterUpdatePosition = true;
- mEventData->mUpdateInputStyle = true;
-
- // Notify the cursor position to the InputMethodContext.
- if( mEventData->mInputMethodContext )
- {
- mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
- mEventData->mInputMethodContext.NotifyCursorPosition();
- }
- }
- else if( 2u == tapCount )
- {
- if( mEventData->mSelectionEnabled )
- {
- // Convert from control's coords to text's coords.
- const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
- const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
- // Calculates the logical position from the x,y coords.
- RepositionSelectionHandles( xPosition,
- yPosition,
- mEventData->mDoubleTapAction );
- }
- }
- }
+ ControllerImplEventHandler::OnTapEvent(*this, event);
}
void Controller::Impl::OnPanEvent( const Event& event )
{
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text input.
- return;
- }
-
- const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
- const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
-
- if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
- {
- // Nothing to do if scrolling is not enabled.
- return;
- }
-
- const GestureState state = static_cast<GestureState>( event.p1.mInt );
- switch( state )
- {
- case GestureState::STARTED:
- {
- // Will remove the cursor, handles or text's popup, ...
- ChangeState( EventData::TEXT_PANNING );
- break;
- }
- case GestureState::CONTINUING:
- {
- const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
- const Vector2 currentScroll = mModel->mScrollPosition;
-
- if( isHorizontalScrollEnabled )
- {
- const float displacementX = event.p2.mFloat;
- mModel->mScrollPosition.x += displacementX;
-
- ClampHorizontalScroll( layoutSize );
- }
-
- if( isVerticalScrollEnabled )
- {
- const float displacementY = event.p3.mFloat;
- mModel->mScrollPosition.y += displacementY;
-
- ClampVerticalScroll( layoutSize );
- }
-
- mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
- break;
- }
- case GestureState::FINISHED:
- case GestureState::CANCELLED: // FALLTHROUGH
- {
- // Will go back to the previous state to show the cursor, handles, the text's popup, ...
- ChangeState( mEventData->mPreviousState );
- break;
- }
- default:
- break;
- }
+ ControllerImplEventHandler::OnPanEvent(*this, event);
}
void Controller::Impl::OnLongPressEvent( const Event& event )
{
- DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
-
- if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
- {
- ChangeState( EventData::EDITING_WITH_POPUP );
- mEventData->mDecoratorUpdated = true;
- mEventData->mUpdateInputStyle = true;
- }
- else
- {
- if( mEventData->mSelectionEnabled )
- {
- // Convert from control's coords to text's coords.
- const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
- const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
- // Calculates the logical position from the x,y coords.
- RepositionSelectionHandles( xPosition,
- yPosition,
- mEventData->mLongPressAction );
- }
- }
+ ControllerImplEventHandler::OnLongPressEvent(*this, event);
}
void Controller::Impl::OnHandleEvent( const Event& event )
{
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text input.
- return;
- }
-
- const unsigned int state = event.p1.mUint;
- const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
- const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
-
- if( HANDLE_PRESSED == state )
- {
- // Convert from decorator's coords to text's coords.
- const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
- const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
- // Need to calculate the handle's new position.
- bool matchedCharacter = false;
- const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- xPosition,
- yPosition,
- CharacterHitTest::SCROLL,
- matchedCharacter );
-
- if( Event::GRAB_HANDLE_EVENT == event.type )
- {
- ChangeState ( EventData::GRAB_HANDLE_PANNING );
-
- if( handleNewPosition != mEventData->mPrimaryCursorPosition )
- {
- // Updates the cursor position if the handle's new position is different than the current one.
- mEventData->mUpdateCursorPosition = true;
- // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
- mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
- mEventData->mPrimaryCursorPosition = handleNewPosition;
- }
-
- // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
- mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
- }
- else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
- {
- ChangeState ( EventData::SELECTION_HANDLE_PANNING );
-
- if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
- ( handleNewPosition != mEventData->mRightSelectionPosition ) )
- {
- // Updates the highlight box if the handle's new position is different than the current one.
- mEventData->mUpdateHighlightBox = true;
- // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
- mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
- mEventData->mLeftSelectionPosition = handleNewPosition;
- }
-
- // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
- mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
-
- // Will define the order to scroll the text to match the handle position.
- mEventData->mIsLeftHandleSelected = true;
- mEventData->mIsRightHandleSelected = false;
- }
- else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
- {
- ChangeState ( EventData::SELECTION_HANDLE_PANNING );
-
- if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
- ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
- {
- // Updates the highlight box if the handle's new position is different than the current one.
- mEventData->mUpdateHighlightBox = true;
- // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
- mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
- mEventData->mRightSelectionPosition = handleNewPosition;
- }
-
- // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
- mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
-
- // Will define the order to scroll the text to match the handle position.
- mEventData->mIsLeftHandleSelected = false;
- mEventData->mIsRightHandleSelected = true;
- }
- } // end ( HANDLE_PRESSED == state )
- else if( ( HANDLE_RELEASED == state ) ||
- handleStopScrolling )
- {
- CharacterIndex handlePosition = 0u;
- if( handleStopScrolling || isSmoothHandlePanEnabled )
- {
- // Convert from decorator's coords to text's coords.
- const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
- const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
- bool matchedCharacter = false;
- handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- xPosition,
- yPosition,
- CharacterHitTest::SCROLL,
- matchedCharacter );
- }
-
- if( Event::GRAB_HANDLE_EVENT == event.type )
- {
- mEventData->mUpdateCursorPosition = true;
- mEventData->mUpdateGrabHandlePosition = true;
- mEventData->mUpdateInputStyle = true;
-
- if( !IsClipboardEmpty() )
- {
- ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
- }
-
- if( handleStopScrolling || isSmoothHandlePanEnabled )
- {
- mEventData->mScrollAfterUpdatePosition = true;
- mEventData->mPrimaryCursorPosition = handlePosition;
- }
- }
- else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
- {
- ChangeState( EventData::SELECTING );
-
- mEventData->mUpdateHighlightBox = true;
- mEventData->mUpdateLeftSelectionPosition = true;
- mEventData->mUpdateRightSelectionPosition = true;
-
- if( handleStopScrolling || isSmoothHandlePanEnabled )
- {
- mEventData->mScrollAfterUpdatePosition = true;
-
- if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
- ( handlePosition != mEventData->mLeftSelectionPosition ) )
- {
- mEventData->mLeftSelectionPosition = handlePosition;
- }
- }
- }
- else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
- {
- ChangeState( EventData::SELECTING );
-
- mEventData->mUpdateHighlightBox = true;
- mEventData->mUpdateRightSelectionPosition = true;
- mEventData->mUpdateLeftSelectionPosition = true;
-
- if( handleStopScrolling || isSmoothHandlePanEnabled )
- {
- mEventData->mScrollAfterUpdatePosition = true;
- if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
- ( handlePosition != mEventData->mLeftSelectionPosition ) )
- {
- mEventData->mRightSelectionPosition = handlePosition;
- }
- }
- }
-
- mEventData->mDecoratorUpdated = true;
- } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
- else if( HANDLE_SCROLLING == state )
- {
- const float xSpeed = event.p2.mFloat;
- const float ySpeed = event.p3.mFloat;
- const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
- const Vector2 currentScrollPosition = mModel->mScrollPosition;
-
- mModel->mScrollPosition.x += xSpeed;
- mModel->mScrollPosition.y += ySpeed;
-
- ClampHorizontalScroll( layoutSize );
- ClampVerticalScroll( layoutSize );
-
- bool endOfScroll = false;
- if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
- {
- // Notify the decorator there is no more text to scroll.
- // The decorator won't send more scroll events.
- mEventData->mDecorator->NotifyEndOfScroll();
- // Still need to set the position of the handle.
- endOfScroll = true;
- }
-
- // Set the position of the handle.
- const bool scrollRightDirection = xSpeed > 0.f;
- const bool scrollBottomDirection = ySpeed > 0.f;
- const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
- const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
-
- if( Event::GRAB_HANDLE_EVENT == event.type )
- {
- ChangeState( EventData::GRAB_HANDLE_PANNING );
-
- // Get the grab handle position in decorator coords.
- Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
-
- if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
- {
- // Position the grag handle close to either the left or right edge.
- position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
- }
-
- if( mEventData->mDecorator->IsVerticalScrollEnabled() )
- {
- position.x = mEventData->mCursorHookPositionX;
-
- // Position the grag handle close to either the top or bottom edge.
- position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
- }
-
- // Get the new handle position.
- // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
- bool matchedCharacter = false;
- const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- position.x - mModel->mScrollPosition.x,
- position.y - mModel->mScrollPosition.y,
- CharacterHitTest::SCROLL,
- matchedCharacter );
-
- if( mEventData->mPrimaryCursorPosition != handlePosition )
- {
- mEventData->mUpdateCursorPosition = true;
- mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
- mEventData->mScrollAfterUpdatePosition = true;
- mEventData->mPrimaryCursorPosition = handlePosition;
- }
- mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
-
- // Updates the decorator if the soft handle panning is enabled.
- mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
- }
- else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
- {
- ChangeState( EventData::SELECTION_HANDLE_PANNING );
-
- // Get the selection handle position in decorator coords.
- Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
-
- if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
- {
- // Position the selection handle close to either the left or right edge.
- position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
- }
-
- if( mEventData->mDecorator->IsVerticalScrollEnabled() )
- {
- position.x = mEventData->mCursorHookPositionX;
-
- // Position the grag handle close to either the top or bottom edge.
- position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
- }
-
- // Get the new handle position.
- // The selection handle's position is in decorator's coords. Need to transform to text's coords.
- bool matchedCharacter = false;
- const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
- mModel->mLogicalModel,
- mMetrics,
- position.x - mModel->mScrollPosition.x,
- position.y - mModel->mScrollPosition.y,
- CharacterHitTest::SCROLL,
- matchedCharacter );
-
- if( leftSelectionHandleEvent )
- {
- const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
-
- if( differentHandles || endOfScroll )
- {
- mEventData->mUpdateHighlightBox = true;
- mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
- mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
- mEventData->mLeftSelectionPosition = handlePosition;
- }
- }
- else
- {
- const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
- if( differentHandles || endOfScroll )
- {
- mEventData->mUpdateHighlightBox = true;
- mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
- mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
- mEventData->mRightSelectionPosition = handlePosition;
- }
- }
-
- if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
- {
- RepositionSelectionHandles();
-
- mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
- }
- }
- mEventData->mDecoratorUpdated = true;
- } // end ( HANDLE_SCROLLING == state )
+ ControllerImplEventHandler::OnHandleEvent(*this, event);
}
void Controller::Impl::OnSelectEvent( const Event& event )
{
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text.
- return;
- }
-
- if( mEventData->mSelectionEnabled )
- {
- // Convert from control's coords to text's coords.
- const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
- const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
- // Calculates the logical position from the x,y coords.
- RepositionSelectionHandles( xPosition,
- yPosition,
- Controller::NoTextTap::HIGHLIGHT );
- }
+ ControllerImplEventHandler::OnSelectEvent(*this, event);
}
void Controller::Impl::OnSelectAllEvent()
{
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
-
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text.
- return;
- }
-
- if( mEventData->mSelectionEnabled )
- {
- // Calculates the logical position from the start.
- RepositionSelectionHandles( 0.f - mModel->mScrollPosition.x,
- 0.f - mModel->mScrollPosition.y,
- Controller::NoTextTap::HIGHLIGHT );
-
- mEventData->mLeftSelectionPosition = 0u;
- mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
- }
+ ControllerImplEventHandler::OnSelectAllEvent(*this);
}
void Controller::Impl::OnSelectNoneEvent()
{
- DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
-
- if( NULL == mEventData )
- {
- // Nothing to do if there is no text.
- return;
- }
-
- if( mEventData->mSelectionEnabled && mEventData->mState == EventData::SELECTING)
- {
- mEventData->mPrimaryCursorPosition = 0u;
- mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
- ChangeState( EventData::INACTIVE );
- mEventData->mUpdateCursorPosition = true;
- mEventData->mUpdateInputStyle = true;
- mEventData->mScrollAfterUpdatePosition = true;
- }
+ ControllerImplEventHandler::OnSelectNoneEvent(*this);
}
void Controller::Impl::SetTextSelectionRange(const uint32_t *pStart, const uint32_t *pEnd)
{
ChangeState( EventData::SELECTING );
mEventData->mUpdateHighlightBox = true;
+ mEventData->mUpdateLeftSelectionPosition = true;
+ mEventData->mUpdateRightSelectionPosition = true;
}
}
}
#define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_H
/*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
//Forward declarations
struct CursorInfo;
struct FontDefaults;
+struct ControllerImplEventHandler;
class SelectableControlInterface;
float mTextFitMaxSize; ///< Maximum Font Size for text fit. Default 100
float mTextFitStepSize; ///< Step Size for font intervalse. Default 1
bool mTextFitEnabled : 1; ///< Whether the text's fit is enabled.
+
+private:
+ friend ControllerImplEventHandler;
};
} // namespace Text
/*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
namespace Internal
{
+namespace
+{
+
+/// Parses a Property::Array and sets up the animator appropriately
+void ParseArray(TransitionData::Animator* animator, const Property::Array* array)
+{
+ bool valid = true;
+ Vector4 controlPoints;
+ if( array && array->Count() >= 4 )
+ {
+ for( size_t vecIdx = 0; vecIdx < 4; ++vecIdx )
+ {
+ const Property::Value& v = array->GetElementAt(vecIdx);
+ if( v.GetType() == Property::FLOAT )
+ {
+ controlPoints[vecIdx] = v.Get<float>();
+ }
+ else
+ {
+ valid = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ valid = false;
+ }
+
+ if( valid )
+ {
+ Vector2 controlPoint1( controlPoints.x, controlPoints.y );
+ Vector2 controlPoint2( controlPoints.z, controlPoints.w );
+ animator->alphaFunction = AlphaFunction( controlPoint1, controlPoint2 );
+ }
+ else
+ {
+ animator->animate = false;
+ }
+}
+
+/// Parses a string value and sets up the animator appropriately
+void ParseString(TransitionData::Animator* animator, std::string alphaFunctionValue)
+{
+ if( alphaFunctionValue == "LINEAR" )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::LINEAR);
+ }
+ else if( ! alphaFunctionValue.compare(0, 5, "EASE_" ) )
+ {
+ if( alphaFunctionValue == "EASE_IN" )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN);
+ }
+ else if( alphaFunctionValue == "EASE_OUT" )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT);
+ }
+ else if( ! alphaFunctionValue.compare( 5, 3, "IN_" ) )
+ {
+ if( ! alphaFunctionValue.compare(8, -1, "SQUARE" ))
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SQUARE);
+ }
+ else if( ! alphaFunctionValue.compare(8, -1, "OUT" ))
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT);
+ }
+ else if( ! alphaFunctionValue.compare(8, -1, "OUT_SINE" ))
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT_SINE);
+ }
+ else if( ! alphaFunctionValue.compare(8, -1, "SINE" ))
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SINE);
+ }
+ }
+ else if( ! alphaFunctionValue.compare( 5, 4, "OUT_" ) )
+ {
+ if( ! alphaFunctionValue.compare(9, -1, "SQUARE" ) )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SQUARE);
+ }
+ else if( ! alphaFunctionValue.compare(9, -1, "SINE" ) )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SINE);
+ }
+ else if( ! alphaFunctionValue.compare(9, -1, "BACK" ) )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_BACK);
+ }
+ }
+ }
+ else if( alphaFunctionValue == "REVERSE" )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::REVERSE);
+ }
+ else if( alphaFunctionValue == "BOUNCE" )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::BOUNCE);
+ }
+ else if( alphaFunctionValue == "SIN" )
+ {
+ animator->alphaFunction = AlphaFunction(AlphaFunction::SIN);
+ }
+}
+} // unnamed namespace
+
TransitionData::TransitionData()
{
}
{
if( value.GetType() == Property::ARRAY )
{
- bool valid = true;
- Vector4 controlPoints;
- const Property::Array* array = value.GetArray();
- if( array && array->Count() >= 4 )
- {
- for( size_t vecIdx = 0; vecIdx < 4; ++vecIdx )
- {
- const Property::Value& v = array->GetElementAt(vecIdx);
- if( v.GetType() == Property::FLOAT )
- {
- controlPoints[vecIdx] = v.Get<float>();
- }
- else
- {
- valid = false;
- break;
- }
- }
- }
- else
- {
- valid = false;
- }
-
- if( valid )
- {
- Vector2 controlPoint1( controlPoints.x, controlPoints.y );
- Vector2 controlPoint2( controlPoints.z, controlPoints.w );
- animator->alphaFunction = AlphaFunction( controlPoint1, controlPoint2 );
- }
- else
- {
- animator->animate = false;
- }
+ ParseArray(animator, value.GetArray());
}
else if( value.GetType() == Property::VECTOR4 )
{
}
else if( value.GetType() == Property::STRING )
{
- std::string alphaFunctionValue = value.Get< std::string >();
-
- if( alphaFunctionValue == "LINEAR" )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::LINEAR);
- }
- else if( ! alphaFunctionValue.compare(0, 5, "EASE_" ) )
- {
- if( alphaFunctionValue == "EASE_IN" )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN);
- }
- else if( alphaFunctionValue == "EASE_OUT" )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT);
- }
- else if( ! alphaFunctionValue.compare( 5, 3, "IN_" ) )
- {
- if( ! alphaFunctionValue.compare(8, -1, "SQUARE" ))
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SQUARE);
- }
- else if( ! alphaFunctionValue.compare(8, -1, "OUT" ))
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT);
- }
- else if( ! alphaFunctionValue.compare(8, -1, "OUT_SINE" ))
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT_SINE);
- }
- else if( ! alphaFunctionValue.compare(8, -1, "SINE" ))
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SINE);
- }
- }
- else if( ! alphaFunctionValue.compare( 5, 4, "OUT_" ) )
- {
- if( ! alphaFunctionValue.compare(9, -1, "SQUARE" ) )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SQUARE);
- }
- else if( ! alphaFunctionValue.compare(9, -1, "SINE" ) )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SINE);
- }
- else if( ! alphaFunctionValue.compare(9, -1, "BACK" ) )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_BACK);
- }
- }
- }
- else if( alphaFunctionValue == "REVERSE" )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::REVERSE);
- }
- else if( alphaFunctionValue == "BOUNCE" )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::BOUNCE);
- }
- else if( alphaFunctionValue == "SIN" )
- {
- animator->alphaFunction = AlphaFunction(AlphaFunction::SIN);
- }
+ ParseString(animator, value.Get< std::string >());
}
else
{
{
const unsigned int TOOLKIT_MAJOR_VERSION = 1;
const unsigned int TOOLKIT_MINOR_VERSION = 9;
-const unsigned int TOOLKIT_MICRO_VERSION = 34;
+const unsigned int TOOLKIT_MICRO_VERSION = 35;
const char* const TOOLKIT_BUILD_DATE = __DATE__ " " __TIME__;
#ifdef DEBUG_ENABLED
Name: dali2-toolkit
Summary: Dali 3D engine Toolkit
-Version: 1.9.34
+Version: 1.9.35
Release: 1
Group: System/Libraries
License: Apache-2.0 and BSD-3-Clause and MIT