Renderer, ...: use PixelRect::GetCenter()
[xcsoar.git] / src / MapWindow / GlueMapWindowDisplayMode.cpp
blob513ab04f40be591530faaf4d8e5b3e432d3bb7fa
1 /*
2 Copyright_License {
4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "GlueMapWindow.hpp"
25 #include "Terrain/RasterTerrain.hpp"
26 #include "Interface.hpp"
27 #include "Profile/Profile.hpp"
28 #include "Screen/Layout.hpp"
29 #include "Util/Clamp.hpp"
31 void
32 OffsetHistory::Reset()
34 offsets.fill(RasterPoint{0, 0});
37 void
38 OffsetHistory::Add(RasterPoint p)
40 offsets[pos] = p;
41 pos = (pos + 1) % offsets.size();
44 RasterPoint
45 OffsetHistory::GetAverage() const
47 int x = 0;
48 int y = 0;
50 for (auto i = offsets.begin(), end = offsets.end(); i != end; ++i) {
51 x += i->x;
52 y += i->y;
55 RasterPoint avg;
56 avg.x = x / (int) offsets.size();
57 avg.y = y / (int) offsets.size();
59 return avg;
62 void
63 GlueMapWindow::SetPan(bool enable)
65 switch (follow_mode) {
66 case FOLLOW_SELF:
67 if (!enable)
68 return;
70 follow_mode = FOLLOW_PAN;
71 break;
73 case FOLLOW_PAN:
74 if (enable)
75 return;
77 follow_mode = FOLLOW_SELF;
78 break;
81 UpdateProjection();
82 FullRedraw();
85 void
86 GlueMapWindow::TogglePan()
88 switch (follow_mode) {
89 case FOLLOW_SELF:
90 follow_mode = FOLLOW_PAN;
91 break;
93 case FOLLOW_PAN:
94 follow_mode = FOLLOW_SELF;
95 break;
98 UpdateProjection();
99 FullRedraw();
102 void
103 GlueMapWindow::PanTo(const GeoPoint &location)
105 follow_mode = FOLLOW_PAN;
106 visible_projection.SetGeoLocation(location);
108 UpdateProjection();
109 FullRedraw();
112 void
113 GlueMapWindow::SetMapScale(fixed scale)
115 MapWindow::SetMapScale(scale);
117 const bool circling =
118 CommonInterface::GetUIState().display_mode == DisplayMode::CIRCLING;
119 MapSettings &settings = CommonInterface::SetMapSettings();
121 if (circling && settings.circle_zoom_enabled)
122 // save cruise scale
123 settings.circling_scale = visible_projection.GetScale();
124 else
125 settings.cruise_scale = visible_projection.GetScale();
127 SaveDisplayModeScales();
130 void
131 GlueMapWindow::SaveDisplayModeScales()
133 const MapSettings &settings = CommonInterface::GetMapSettings();
135 Profile::Set(ProfileKeys::ClimbMapScale, (int)(settings.circling_scale * 10000));
136 Profile::Set(ProfileKeys::CruiseMapScale, (int)(settings.cruise_scale * 10000));
139 void
140 GlueMapWindow::SwitchZoomClimb(bool circling)
142 const MapSettings &settings = CommonInterface::GetMapSettings();
144 if (!settings.circle_zoom_enabled)
145 return;
147 if (circling)
148 visible_projection.SetScale(settings.circling_scale);
149 else
150 visible_projection.SetScale(settings.cruise_scale);
153 void
154 GlueMapWindow::UpdateDisplayMode()
156 /* not using MapWindowBlackboard here because these methods are
157 called by the main thread */
158 enum DisplayMode new_mode = CommonInterface::GetUIState().display_mode;
160 const bool was_circling = last_display_mode == DisplayMode::CIRCLING;
161 const bool is_circling = new_mode == DisplayMode::CIRCLING;
163 if (!was_circling && is_circling)
164 offset_history.Reset();
166 last_display_mode = new_mode;
168 if (is_circling != was_circling)
169 SwitchZoomClimb(is_circling);
172 void
173 GlueMapWindow::UpdateScreenAngle()
175 /* not using MapWindowBlackboard here because these methods are
176 called by the main thread */
177 const NMEAInfo &basic = CommonInterface::Basic();
178 const DerivedInfo &calculated = CommonInterface::Calculated();
179 const MapSettings &settings = CommonInterface::GetMapSettings();
180 const UIState &ui_state = CommonInterface::GetUIState();
182 DisplayOrientation orientation =
183 ui_state.display_mode == DisplayMode::CIRCLING
184 ? settings.circling_orientation
185 : settings.cruise_orientation;
187 if (orientation == DisplayOrientation::TARGET_UP &&
188 calculated.task_stats.current_leg.vector_remaining.IsValid())
189 visible_projection.SetScreenAngle(calculated.task_stats.current_leg.
190 vector_remaining.bearing);
191 else if (orientation == DisplayOrientation::HEADING_UP)
192 visible_projection.SetScreenAngle(
193 basic.attitude.IsHeadingUseable() ? basic.attitude.heading : Angle::Zero());
194 else if (orientation == DisplayOrientation::NORTH_UP)
195 visible_projection.SetScreenAngle(Angle::Zero());
196 else if (orientation == DisplayOrientation::WIND_UP &&
197 calculated.wind_available &&
198 calculated.wind.norm >= fixed(0.5))
199 visible_projection.SetScreenAngle(calculated.wind.bearing);
200 else
201 // normal, glider forward
202 visible_projection.SetScreenAngle(
203 basic.track_available ? basic.track : Angle::Zero());
205 compass_visible = orientation != DisplayOrientation::NORTH_UP;
208 void
209 GlueMapWindow::UpdateMapScale()
211 /* not using MapWindowBlackboard here because these methods are
212 called by the main thread */
213 const DerivedInfo &calculated = CommonInterface::Calculated();
214 const MapSettings &settings = CommonInterface::GetMapSettings();
215 const bool circling =
216 CommonInterface::GetUIState().display_mode == DisplayMode::CIRCLING;
218 if (circling && settings.circle_zoom_enabled)
219 return;
221 if (!IsNearSelf())
222 return;
224 fixed distance = calculated.auto_zoom_distance;
225 if (settings.auto_zoom_enabled && positive(distance)) {
226 // Calculate distance percentage between plane symbol and map edge
227 // 50: centered 100: at edge of map
228 int auto_zoom_factor = circling
229 ? 50
230 : 100 - settings.glider_screen_position;
232 // Leave 5% of full distance for target display
233 auto_zoom_factor -= 5;
234 // Adjust to account for map scale units
235 auto_zoom_factor *= 8;
237 distance /= fixed(auto_zoom_factor) / 100;
239 // Clip map auto zoom range to reasonable values
240 distance = Clamp(distance, fixed(525),
241 settings.max_auto_zoom_distance / 10);
243 visible_projection.SetFreeMapScale(distance);
247 void
248 GlueMapWindow::SetLocationLazy(const GeoPoint location)
250 if (!visible_projection.IsValid()) {
251 SetLocation(location);
252 return;
255 const fixed distance_meters =
256 visible_projection.GetGeoLocation().Distance(location);
257 const fixed distance_pixels =
258 visible_projection.DistanceMetersToPixels(distance_meters);
259 if (distance_pixels > fixed(0.5))
260 SetLocation(location);
263 void
264 GlueMapWindow::UpdateProjection()
266 const PixelRect rc = GetClientRect();
268 /* not using MapWindowBlackboard here because these methods are
269 called by the main thread */
270 const NMEAInfo &basic = CommonInterface::Basic();
271 const DerivedInfo &calculated = CommonInterface::Calculated();
272 const MapSettings &settings_map = CommonInterface::GetMapSettings();
273 const bool circling =
274 CommonInterface::GetUIState().display_mode == DisplayMode::CIRCLING;
276 const RasterPoint center = rc.GetCenter();
278 if (circling || !IsNearSelf())
279 visible_projection.SetScreenOrigin(center.x, center.y);
280 else if (settings_map.cruise_orientation == DisplayOrientation::NORTH_UP ||
281 settings_map.cruise_orientation == DisplayOrientation::WIND_UP) {
282 RasterPoint offset{0, 0};
283 if (settings_map.glider_screen_position != 50 &&
284 settings_map.map_shift_bias != MapShiftBias::NONE) {
285 fixed x = fixed(0);
286 fixed y = fixed(0);
287 if (settings_map.map_shift_bias == MapShiftBias::TRACK) {
288 if (basic.track_available &&
289 basic.ground_speed_available &&
290 /* 8 m/s ~ 30 km/h */
291 basic.ground_speed > fixed(8)) {
292 auto angle = basic.track.Reciprocal() - visible_projection.GetScreenAngle();
294 const auto sc = angle.SinCos();
295 x = sc.first;
296 y = sc.second;
298 } else if (settings_map.map_shift_bias == MapShiftBias::TARGET) {
299 if (calculated.task_stats.current_leg.solution_remaining.IsDefined()) {
300 auto angle = calculated.task_stats.current_leg.solution_remaining
301 .vector.bearing.Reciprocal() - visible_projection.GetScreenAngle();
303 const auto sc = angle.SinCos();
304 x = sc.first;
305 y = sc.second;
308 fixed position_factor = fixed(50 - settings_map.glider_screen_position) / 100;
309 offset.x = PixelScalar(x * (rc.right - rc.left) * position_factor);
310 offset.y = PixelScalar(y * (rc.top - rc.bottom) * position_factor);
311 offset_history.Add(offset);
312 offset = offset_history.GetAverage();
314 visible_projection.SetScreenOrigin(center.x + offset.x, center.y + offset.y);
315 } else
316 visible_projection.SetScreenOrigin(center.x,
317 ((rc.top - rc.bottom) * settings_map.glider_screen_position / 100) + rc.bottom);
319 if (!IsNearSelf()) {
320 /* no-op - the Projection's location is updated manually */
321 } else if (circling && calculated.thermal_locator.estimate_valid) {
322 const fixed d_t = calculated.thermal_locator.estimate_location.Distance(basic.location);
323 if (!positive(d_t)) {
324 SetLocationLazy(basic.location);
325 } else {
326 const fixed d_max = Double(visible_projection.GetMapScale());
327 const fixed t = std::min(d_t, d_max)/d_t;
328 SetLocation(basic.location.Interpolate(calculated.thermal_locator.estimate_location,
329 t));
331 } else if (basic.location_available)
332 // Pan is off
333 SetLocationLazy(basic.location);
334 else if (!visible_projection.IsValid() && terrain != nullptr)
335 /* if there's no GPS fix yet and no home waypoint, start at the
336 map center, to avoid showing a fully white map, which confuses
337 users */
338 SetLocation(terrain->GetTerrainCenter());
340 visible_projection.UpdateScreenBounds();