The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
frame_private.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2006 - 2025
3 by Jeremy Rosen <jeremy.rosen@enst-bretagne.fr>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#pragma once
17
18#include "lexical_cast.hpp"
20
21#include <chrono>
22#include <vector>
23
24namespace image { class locator; }
25using namespace std::chrono_literals;
26
27template<typename T, typename D>
29{
30public:
31 using data_t = std::vector<std::pair<D, std::chrono::milliseconds>>;
32
33 progressive_base(const std::string& input)
34 : data_()
35 , input_(input)
36 {}
37
38 virtual const T get_current_element(const std::chrono::milliseconds& current_time, T default_val) const = 0;
39
40 virtual bool does_not_change() const
41 {
42 return data_.size() <= 1;
43 }
44
45 std::chrono::milliseconds duration() const
46 {
47 std::chrono::milliseconds total{0};
48 for(const auto& entry : data_) {
49 total += entry.second;
50 }
51
52 return total;
53 }
54
55 std::string get_original() const
56 {
57 return input_;
58 }
59
61 {
62 return data_;
63 }
64
65 const data_t& data() const
66 {
67 return data_;
68 }
69
70 virtual ~progressive_base() {}
71
72private:
74 std::string input_;
75};
76
77template<typename T>
78class progressive_pair : public progressive_base<T, std::pair<T, T>>
79{
80public:
81 progressive_pair(const std::string& input = "", const std::chrono::milliseconds& duration = std::chrono::milliseconds{0})
82 : progressive_base<T, std::pair<T, T>>(input)
83 {
84 auto& base_data = progressive_pair_base_type::data();
85
86 const int split_flag = utils::REMOVE_EMPTY; // useless to strip spaces
87
88 const std::vector<std::string> comma_split = utils::split(input, ',', split_flag);
89 const auto time_chunk = std::max(1ms, duration / std::max<int>(comma_split.size(), 1));
90
91 for(const auto& entry : comma_split) {
92 std::vector<std::string> colon_split = utils::split(entry, ':', split_flag);
93 auto time = 0ms;
94
95 try {
96 time = (colon_split.size() > 1) ? std::chrono::milliseconds{std::stoi(colon_split[1])} : time_chunk;
97 } catch(const std::invalid_argument&) {
98 //ERR_NG << "Invalid time in unit animation: " << colon_split[1];
99 }
100
101 try {
102 std::vector<std::string> range = utils::split(colon_split[0],'~',split_flag);
103 T range0 = lexical_cast<T>(range[0]);
104 T range1 = (range.size() > 1) ? lexical_cast<T>(range[1]) : range0;
105
106 base_data.push_back({{range0, range1}, time});
107 } catch(const bad_lexical_cast&) {}
108 }
109 }
110
111 virtual const T get_current_element(const std::chrono::milliseconds& current_time, T default_val = T()) const override
112 {
113 const auto& base_data = progressive_pair_base_type::data();
114 const std::chrono::milliseconds& base_duration = progressive_pair_base_type::duration();
115
116 if(base_data.empty()) {
117 return default_val;
118 }
119
120 auto time = 0ms;
121 unsigned int i = 0;
122 const std::chrono::milliseconds searched_time = std::clamp(current_time, 0ms, base_duration);
123
124 while(time < searched_time && i < base_data.size()) {
125 time += base_data[i].second;
126 ++i;
127 }
128
129 if(i != 0) {
130 i--;
131 time -= base_data[i].second;
132 }
133
134 const auto [first, second] = base_data[i].first;
135 using fractional_milliseconds = std::chrono::duration<double, std::milli>;
136
137 return T((
138 fractional_milliseconds{searched_time - time} /
139 fractional_milliseconds{base_data[i].second}
140 ) * (second - first) + first);
141 }
142
143 bool does_not_change() const override
144 {
145 const auto& base_data = progressive_pair_base_type::data();
146 return base_data.empty() || (base_data.size() == 1 && base_data[0].first.first == base_data[0].first.second);
147 }
148
149private:
151};
152
153template<typename T>
155{
156public:
157 progressive_single(const std::string& input = "", const std::chrono::milliseconds& duration = std::chrono::milliseconds{0})
158 : progressive_base<T, T>(input)
159 {
160 auto& base_data = progressive_single_base_type::data();
161
162 const std::vector<std::string> first_pass = utils::square_parenthetical_split(input);
163 auto time_chunk = std::max(duration, 1ms);
164
165 if(duration > 1ms && !first_pass.empty()) {
166 // If duration specified, divide evenly the time for items with unspecified times
167 auto total_specified_time = 0ms;
168
169 for(const std::string& fp_string : first_pass) {
170 std::vector<std::string> second_pass = utils::split(fp_string, ':');
171 if(second_pass.size() > 1) {
172 try {
173 total_specified_time += std::chrono::milliseconds{std::stoi(second_pass[1])};
174 } catch(const std::invalid_argument&) {
175 //ERR_NG << "Invalid time in unit animation: " << second_pass[1];
176 }
177 }
178 }
179
180 // Fun Fact: since std::size_t is unsigned, template argument deduction does not yield chrono::milliseconds,
181 // but rather duration<unsigned long long, std::milli>. That's why we explicitly cast to milliseconds.
182 std::chrono::milliseconds new_time_chunk{(duration - total_specified_time) / first_pass.size()};
183 time_chunk = std::max(new_time_chunk, 1ms);
184 }
185
186 for(const std::string& fp_string : first_pass) {
187 std::vector<std::string> second_pass = utils::split(fp_string, ':');
188 if(second_pass.size() > 1) {
189 try {
190 std::chrono::milliseconds time{std::stoi(second_pass[1])};
191 base_data.emplace_back(std::move(second_pass[0]), time);
192 } catch(const std::invalid_argument&) {
193 //ERR_NG << "Invalid time in unit animation: " << second_pass[1];
194 }
195 } else {
196 base_data.emplace_back(std::move(second_pass[0]), time_chunk);
197 }
198 }
199 }
200
201 virtual const T get_current_element(const std::chrono::milliseconds& current_time, T default_val = T()) const override
202 {
203 const auto& base_data = progressive_single_base_type::data();
204
205 if(base_data.empty()) {
206 return default_val;
207 }
208
209 auto time = 0ms;
210 unsigned int i = 0;
211
212 while(time < current_time && i < base_data.size()) {
213 time += base_data[i].second;
214 ++i;
215 }
216
217 // TODO: what is this for?
218 // ^ Seems that the value of i at this point is the first index whose time is *greater* than current_time,
219 // so the index needs to be adjusted to give the first whose time is less (or equal).
220 if(i) {
221 i--;
222 }
223
224 return base_data[i].first;
225 }
226
227private:
229};
230
231// Common types used by the unit frame code.
234
virtual ~progressive_base()
std::vector< std::pair< D, std::chrono::milliseconds > > data_t
progressive_base(const std::string &input)
virtual const T get_current_element(const std::chrono::milliseconds &current_time, T default_val) const =0
std::string get_original() const
const data_t & data() const
virtual bool does_not_change() const
std::chrono::milliseconds duration() const
progressive_pair(const std::string &input="", const std::chrono::milliseconds &duration=std::chrono::milliseconds{0})
virtual const T get_current_element(const std::chrono::milliseconds &current_time, T default_val=T()) const override
bool does_not_change() const override
progressive_single(const std::string &input="", const std::chrono::milliseconds &duration=std::chrono::milliseconds{0})
virtual const T get_current_element(const std::chrono::milliseconds &current_time, T default_val=T()) const override
New lexcical_cast header.
Functions to load and save images from/to disk.
STL namespace.
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::vector< std::string > split(const config_attribute_value &val)
Thrown when a lexical_cast fails.
#define i