-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
userLocationMarker: Redesign location marker
Use the accent color to draw the user location marker, drawn in code rather than using a static icon. Also use the accent color to draw the accuracy circle and implement the "torch" widget indicating heading as per the mockups.
- Loading branch information
Showing
1 changed file
with
196 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,14 +19,21 @@ | |
* Author: Damián Nohales <[email protected]> | ||
*/ | ||
|
||
import Cairo from 'cairo'; | ||
import Adw from 'gi://Adw'; | ||
import Gdk from 'gi://Gdk'; | ||
import GObject from 'gi://GObject'; | ||
import Graphene from 'gi://Graphene'; | ||
import Gsk from 'gi://Gsk'; | ||
import Shumate from 'gi://Shumate'; | ||
|
||
import {IconMarker} from './iconMarker.js'; | ||
import {MapMarker} from './mapMarker.js'; | ||
|
||
const LOCATION_MARKER_SIZE = 32; | ||
const LOCATION_MARKER_MARGIN = 4; | ||
const LOCATION_MARKER_SHADOW_RADIUS = 4; | ||
const WHITE = new Gdk.RGBA({ red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 }); | ||
const SHADOW_COLOR = new Gdk.RGBA({ red: 0.0, green: 0.0, blue: 0.0, alpha: 0.05 }); | ||
|
||
export class AccuracyCircleMarker extends Shumate.Marker { | ||
|
||
constructor({place, ...params}) { | ||
|
@@ -39,6 +46,23 @@ export class AccuracyCircleMarker extends Shumate.Marker { | |
}); | ||
|
||
this._place = place; | ||
this._styleManager = Adw.StyleManager.get_default(); | ||
this._pathBuilder = new Gsk.PathBuilder(); | ||
} | ||
|
||
vfunc_map() { | ||
this._accentId = this._styleManager.connect('notify::accent-color-rgba', () => { | ||
this.queue_draw(); | ||
}); | ||
this.queue_draw(); | ||
|
||
super.vfunc_map(); | ||
} | ||
|
||
vfunc_unmap() { | ||
this._styleManager.disconnect(this._accentId); | ||
|
||
super.vfunc_unmap(); | ||
} | ||
|
||
refreshGeometry(mapView) { | ||
|
@@ -63,31 +87,130 @@ export class AccuracyCircleMarker extends Shumate.Marker { | |
} | ||
|
||
vfunc_snapshot(snapshot) { | ||
let {x, y, width, height} = this.get_allocation(); | ||
let rect = new Graphene.Rect(); | ||
const width = this.get_width(); | ||
const height = this.get_height(); | ||
const accentColor = this._styleManager.accent_color_rgba; | ||
const center = new Graphene.Point({ x: width / 2, y: height / 2 }); | ||
|
||
this._pathBuilder.add_circle(center, width / 2); | ||
snapshot.append_fill(this._pathBuilder.to_path(), | ||
Gsk.FILL_RULE_EVEN_ODD, | ||
new Gdk.RGBA({ red: accentColor.red, | ||
green: accentColor.green, | ||
blue: accentColor.blue, | ||
alpha: 0.05 })); | ||
|
||
this._pathBuilder.add_circle(center, width / 2); | ||
snapshot.append_stroke(this._pathBuilder.to_path(), | ||
new Gsk.Stroke(1), | ||
new Gdk.RGBA({ red: accentColor.red, | ||
green: accentColor.green, | ||
blue: accentColor.blue, | ||
alpha: 0.15 })); | ||
|
||
super.vfunc_snapshot(snapshot); | ||
} | ||
} | ||
|
||
GObject.registerClass(AccuracyCircleMarker); | ||
|
||
class HeadingTorch extends Shumate.Marker { | ||
constructor({place, mapView, ...params}) { | ||
super({ | ||
...params, | ||
latitude: place.location.latitude, | ||
longitude: place.location.longitude, | ||
can_focus: false, | ||
can_target: false | ||
}); | ||
|
||
this._place = place; | ||
this._mapView = mapView; | ||
this._styleManager = Adw.StyleManager.get_default(); | ||
this._pathBuilder = new Gsk.PathBuilder(); | ||
this._place.connect('notify::location', this.refresh.bind(this)); | ||
this._mapView.map.viewport.connect('notify::rotation', | ||
this.refresh.bind(this)); | ||
this.set_size_request(LOCATION_MARKER_SIZE * 2.5, | ||
LOCATION_MARKER_SIZE * 2.5); | ||
this.refresh(); | ||
} | ||
|
||
refresh() { | ||
if (this._place.location.heading === -1) { | ||
this.visible = false; | ||
} else { | ||
this.latitude = this._place.location.latitude; | ||
this.longitude = this._place.location.longitude; | ||
this.visible = true; | ||
this.queue_draw(); | ||
} | ||
} | ||
|
||
rect.init(0, 0, width, height); | ||
vfunc_map() { | ||
this._accentId = this._styleManager.connect('notify::accent-color-rgba', () => { | ||
this.queue_draw(); | ||
}); | ||
this.queue_draw(); | ||
|
||
let cr = snapshot.append_cairo(rect); | ||
super.vfunc_map(); | ||
} | ||
|
||
cr.setOperator(Cairo.Operator.OVER); | ||
vfunc_unmap() { | ||
this._styleManager.disconnect(this._accentId); | ||
|
||
super.vfunc_unmap(); | ||
} | ||
|
||
vfunc_snapshot(snapshot) { | ||
const accentColor = this._styleManager.accent_color_rgba; | ||
const r = this.get_width() / 2; | ||
const angle = this._place.location.heading + | ||
this._mapView.map.viewport.rotation * 180 / Math.PI | ||
const center = new Graphene.Point({ x: r, y: r }); | ||
const bounds = new Graphene.Rect(); | ||
const gradientStart = | ||
new Gsk.ColorStop({ offset: 0, | ||
color: new Gdk.RGBA({ red: accentColor.red, | ||
green: accentColor.green, | ||
blue: accentColor.blue, | ||
alpha: 0.5 }) }); | ||
const gradientEnd = | ||
new Gsk.ColorStop({ offset: 1, | ||
color: new Gdk.RGBA({ red: accentColor.red, | ||
green: accentColor.green, | ||
blue: accentColor.blue, | ||
alpha: 0.0 }) }); | ||
|
||
cr.setSourceRGBA(0, 0, 255, 0.1); | ||
cr.arc(width / 2, width / 2, width / 2, 0, Math.PI * 2); | ||
cr.fillPreserve(); | ||
bounds.init(-r, -r, 2 * r, 2 * r); | ||
|
||
this._pathBuilder.line_to(-r * Math.sqrt(2) / 2, | ||
-r * Math.sqrt(2) / 2); | ||
this._pathBuilder.arc_to(0, -r * Math.sqrt(2), | ||
r * Math.sqrt(2) / 2, | ||
-r * Math.sqrt(2) / 2); | ||
this._pathBuilder.line_to(0, 0); | ||
snapshot.translate(center); | ||
snapshot.rotate(angle); | ||
snapshot.push_fill(this._pathBuilder.to_path(), Gsk.FILL_RULE_EVEN_ODD); | ||
snapshot.append_radial_gradient(bounds, new Graphene.Point(), r, r, 0.4, 1, | ||
[gradientStart, gradientEnd]); | ||
snapshot.pop(); | ||
|
||
super.vfunc_snapshot(snapshot); | ||
} | ||
} | ||
|
||
GObject.registerClass(AccuracyCircleMarker); | ||
GObject.registerClass(HeadingTorch); | ||
|
||
export class UserLocationMarker extends IconMarker { | ||
export class UserLocationMarker extends MapMarker { | ||
|
||
constructor(params) { | ||
super(params); | ||
|
||
this._accuracyMarker = new AccuracyCircleMarker({ place: this.place }); | ||
this._headingTorch = new HeadingTorch({ place: this.place, | ||
mapView: this._mapView }); | ||
this.connect('notify::view-zoom-level', | ||
() => this._accuracyMarker.refreshGeometry(this._mapView)); | ||
this._mapView.connect('notify::default-width', | ||
|
@@ -97,12 +220,27 @@ export class UserLocationMarker extends IconMarker { | |
this._accuracyMarker.refreshGeometry(this._mapView); | ||
|
||
this.place.connect('notify::location', () => this._updateLocation()); | ||
this._image.pixel_size = 24; | ||
this._updateLocation(); | ||
|
||
this.connect('notify::visible', this._updateAccuracyCircle.bind(this)); | ||
this._mapView.map.viewport.connect('notify::rotation', | ||
() => this._updateLocation()); | ||
this.connect('notify::visible', this._updateLocation.bind(this)); | ||
|
||
this._styleManager = Adw.StyleManager.get_default(); | ||
this.set_size_request(LOCATION_MARKER_SIZE, LOCATION_MARKER_SIZE); | ||
this._pathBuilder = new Gsk.PathBuilder(); | ||
} | ||
|
||
vfunc_map() { | ||
this._accentId = this._styleManager.connect('notify::accent-color-rgba', () => { | ||
this.queue_draw(); | ||
}); | ||
this.queue_draw(); | ||
|
||
super.vfunc_map(); | ||
} | ||
|
||
vfunc_unmap() { | ||
this._styleManager.disconnect(this._accentId); | ||
|
||
super.vfunc_unmap(); | ||
} | ||
|
||
_hasBubble() { | ||
|
@@ -111,18 +249,21 @@ export class UserLocationMarker extends IconMarker { | |
|
||
addToLayer(layer) { | ||
layer.add_marker(this._accuracyMarker); | ||
layer.add_marker(this._headingTorch); | ||
layer.add_marker(this); | ||
} | ||
|
||
_updateLocation() { | ||
if (this.place.location.heading > -1) { | ||
this._image.icon_name = 'user-location-compass' | ||
this.queue_draw(); | ||
this._updateAccuracyCircle(); | ||
this._updateHeadingTorch(); | ||
} | ||
|
||
_updateHeadingTorch() { | ||
if (this.visible) { | ||
this._headingTorch.refresh(); | ||
} else { | ||
this._image.icon_name = 'user-location'; | ||
this._headingTorch.visible = false; | ||
} | ||
|
||
this._updateAccuracyCircle(); | ||
} | ||
|
||
_updateAccuracyCircle() { | ||
|
@@ -134,26 +275,40 @@ export class UserLocationMarker extends IconMarker { | |
} | ||
|
||
vfunc_snapshot(snapshot) { | ||
snapshot.save(); | ||
|
||
if (this.place.location.heading > -1) { | ||
// rotate around the center of the icon | ||
let width = this.get_width(); | ||
let height = this.get_height(); | ||
let point = new Graphene.Point(); | ||
let rotation = this.place.location.heading + | ||
this._mapView.map.viewport.rotation * 180 / Math.PI; | ||
|
||
point.init(width / 2, height / 2); | ||
snapshot.translate(point); | ||
snapshot.rotate(rotation); | ||
point.init(-width / 2, -height / 2); | ||
snapshot.translate(point); | ||
} | ||
const accentColor = this._styleManager.accent_color_rgba; | ||
const center = new Graphene.Point({ x: LOCATION_MARKER_SIZE / 2, | ||
y: LOCATION_MARKER_SIZE / 2 }); | ||
const shadowBounds = new Graphene.Rect(); | ||
|
||
shadowBounds.init(-LOCATION_MARKER_SHADOW_RADIUS, | ||
-LOCATION_MARKER_SHADOW_RADIUS, | ||
LOCATION_MARKER_SIZE + 2 * LOCATION_MARKER_SHADOW_RADIUS, | ||
LOCATION_MARKER_SIZE + 2 * LOCATION_MARKER_SHADOW_RADIUS); | ||
|
||
// draw shadow | ||
shadowBounds.init(0, 0, LOCATION_MARKER_SIZE, LOCATION_MARKER_SIZE); | ||
const shadowOutline = new Gsk.RoundedRect(); | ||
const corner = new Graphene.Size({ width: LOCATION_MARKER_SIZE / 2, | ||
height: LOCATION_MARKER_SIZE / 2 }); | ||
|
||
shadowOutline.init(shadowBounds, corner, corner, corner, corner); | ||
|
||
snapshot.append_outset_shadow(shadowOutline, SHADOW_COLOR, 0, 0, | ||
LOCATION_MARKER_SHADOW_RADIUS, | ||
LOCATION_MARKER_SHADOW_RADIUS); | ||
|
||
// draw outer white circle | ||
this._pathBuilder.add_circle(center, LOCATION_MARKER_SIZE / 2); | ||
snapshot.append_fill(this._pathBuilder.to_path(), | ||
Gsk.FILL_RULE_EVEN_ODD, WHITE); | ||
|
||
// draw inner accent-colored circle | ||
this._pathBuilder.add_circle(center, LOCATION_MARKER_SIZE / 2 - | ||
LOCATION_MARKER_MARGIN); | ||
snapshot.append_fill(this._pathBuilder.to_path(), | ||
Gsk.FILL_RULE_EVEN_ODD, accentColor); | ||
|
||
this.snapshot_child(this._image, snapshot); | ||
super.vfunc_snapshot(snapshot); | ||
snapshot.restore(); | ||
} | ||
} | ||
|
||
|