diff --git a/src/colors/HSB.vala b/src/colors/HSB.vala index 5b369f1..6b44e5b 100644 --- a/src/colors/HSB.vala +++ b/src/colors/HSB.vala @@ -19,4 +19,72 @@ * Authored by: Marius Meisenzahl */ -public class Colors.HSB {} +public class Colors.HSB { + public uint16 hue; + public uint8 saturation; + public uint8 brightness; + + public HSB () {} + + public HSB.from_rgb (RGB rgb) { + double min, max, delta; + double h, s, b; + + double red = rgb.red / 255.0; + double green = rgb.green / 255.0; + double blue = rgb.blue / 255.0; + + min = red; + min = green < min ? green : min; + min = blue < min ? blue : min; + + max = red; + max = green > max ? green : max; + max = blue > max ? blue : max; + + b = max; + delta = max - min; + + if (max != 0) { + s = delta / max; + } else { + s = 0; + h = 0; + + hue = (uint16) (h + 0.5); + saturation = (uint8) (s * 100 + 0.5); + brightness = (uint8) (b * 100 + 0.5); + + return; + } + + if (max == min) { + h = 0; + s = 0; + + hue = (uint16) (h + 0.5); + saturation = (uint8) (s * 100 + 0.5); + brightness = (uint8) (b * 100 + 0.5); + + return; + } + + if (red == max) { + h = (green - blue) / delta; + } else if (green == max) { + h = 2 + (blue - red) / delta; + } else { + h = 4 + (red - green) / delta; + } + + h *= 60; + + if (h < 0) { + h += 360; + } + + hue = (uint16) (h + 0.5); + saturation = (uint8) (s * 100 + 0.5); + brightness = (uint8) (b * 100 + 0.5); + } +} diff --git a/src/colors/RGB.vala b/src/colors/RGB.vala index 86bd2b7..d452df3 100644 --- a/src/colors/RGB.vala +++ b/src/colors/RGB.vala @@ -29,4 +29,75 @@ public class Colors.RGB { public RGB.from_hex (string hex) { hex.scanf ("%02x%02x%02x", &red, &green, &blue); } + + public RGB.from_hsb (HSB hsb) { + int i; + double f, p, q, t; + double r, g, b; + + double hue, saturation, brightness; + hue = (double) hsb.hue; + saturation = (double) (hsb.saturation / 100.0); + brightness = (double) (hsb.brightness / 100.0); + + if (saturation == 0) { + r = brightness; + g = brightness; + b = brightness; + + red = (uint8) (r * 255 + 0.5); + green = (uint8) (g * 255 + 0.5); + blue = (uint8) (b * 255 + 0.5); + + return; + } + + hue /= 60; + i = (int) hue; + f = hue - i; + p = brightness * (1 - saturation); + q = brightness * (1 - saturation * f); + t = brightness * (1 - saturation * (1 - f)); + + switch (i) { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + default: + r = brightness; + g = p; + b = q; + break; + } + + red = (uint8) (r * 255 + 0.5); + green = (uint8) (g * 255 + 0.5); + blue = (uint8) (b * 255 + 0.5); + } + + public string to_hex () { + return "#%02x%02x%02x".printf (red, green, blue); + } } diff --git a/src/controllers/DeviceController.vala b/src/controllers/DeviceController.vala index cadd20f..fed60ca 100644 --- a/src/controllers/DeviceController.vala +++ b/src/controllers/DeviceController.vala @@ -34,6 +34,8 @@ public abstract class Controllers.DeviceController : Object { public abstract void switch_brightness (uint16 brightness); + public abstract void switch_hsb (uint16 hue, uint16 saturation, uint16 brightness); + public abstract void switch_color_temperature (uint16 color_temperature); public abstract void switch_power (bool on); diff --git a/src/lifx/Controller.vala b/src/lifx/Controller.vala index 16f3439..3518f4f 100644 --- a/src/lifx/Controller.vala +++ b/src/lifx/Controller.vala @@ -51,6 +51,15 @@ public class Lifx.Controller : Controllers.DeviceController { lamp.brightness = brightness; } + public override void switch_hsb (uint16 hue, uint16 saturation, uint16 brightness) { + var lamp = device as Lifx.Lamp; + service.set_color (lamp, hue, saturation, brightness, 0, 0); + + lamp.hue = hue; + lamp.saturation = saturation; + lamp.brightness = brightness; + } + public override void switch_color_temperature (uint16 color_temperature) { var lamp = device as Lifx.Lamp; service.set_color (lamp, 0, 0, lamp.brightness, color_temperature, 0); diff --git a/src/meson.build b/src/meson.build index be50a21..f49fff9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -36,6 +36,7 @@ sources = [ 'views/Overview.vala', 'widgets/Carousel.vala', 'widgets/CarouselItem.vala', + 'widgets/ColorPicker.vala', 'widgets/IconPopover.vala', 'widgets/Overlay.vala', 'MainWindow.vala' diff --git a/src/pages/DevicePage.vala b/src/pages/DevicePage.vala index e964e81..ffff36a 100644 --- a/src/pages/DevicePage.vala +++ b/src/pages/DevicePage.vala @@ -21,6 +21,9 @@ public class Pages.DevicePage : Pages.AbstractDevicePage { private Controllers.DeviceController controller; + private Gtk.Scale hue_scale; + private Gtk.Scale saturation_scale; + private Gtk.Scale brightness_scale; public DevicePage (Models.Device device) { Object ( @@ -43,7 +46,7 @@ public class Pages.DevicePage : Pages.AbstractDevicePage { var hue_label = new Gtk.Label (_("Hue: ")); hue_label.xalign = 1; - var hue_scale = new Gtk.Scale.with_range ( + hue_scale = new Gtk.Scale.with_range ( Gtk.Orientation.HORIZONTAL, lamp.hue_min, lamp.hue_max, 1.0 ); @@ -63,7 +66,7 @@ public class Pages.DevicePage : Pages.AbstractDevicePage { var saturation_label = new Gtk.Label (_("Saturation: ")); saturation_label.xalign = 1; - var saturation_scale = new Gtk.Scale.with_range ( + saturation_scale = new Gtk.Scale.with_range ( Gtk.Orientation.HORIZONTAL, lamp.saturation_min, lamp.saturation_max, 1.0 ); @@ -85,7 +88,7 @@ public class Pages.DevicePage : Pages.AbstractDevicePage { var brightness_label = new Gtk.Label (_("Brightness: ")); brightness_label.xalign = 1; - var brightness_scale = new Gtk.Scale.with_range ( + brightness_scale = new Gtk.Scale.with_range ( Gtk.Orientation.HORIZONTAL, lamp.brightness_min, lamp.brightness_max, 1.0 ); @@ -124,6 +127,42 @@ public class Pages.DevicePage : Pages.AbstractDevicePage { content_area.attach (color_temperature_label, 0, 3, 1, 1); content_area.attach (color_temperature_scale, 1, 3, 1, 1); } + + if (lamp.supports_color) { + var c = new Colors.HSB (); + c.hue = remap_value (lamp.hue, lamp.hue_min, lamp.hue_max, 0, 360); + c.saturation = (uint8) remap_value (lamp.saturation, lamp.saturation_min, lamp.saturation_max, 0, 100); + c.brightness = (uint8) remap_value (lamp.brightness, lamp.brightness_min, lamp.brightness_max, 0, 100); + + var color_picker = new Widgets.ColorPicker (MainWindow.get_default ()); + color_picker.hsb = c; + color_picker.on_color_change.connect ((rgb) => { + var hsb = new Colors.HSB.from_rgb (rgb); + + var hue = remap_value (hsb.hue, 0, 360, lamp.hue_min, lamp.hue_max); + var saturation = remap_value (hsb.saturation, 0, 100, lamp.saturation_min, lamp.saturation_max); + var brightness = remap_value (hsb.brightness, 0, 100, lamp.brightness_min, lamp.brightness_max); + + #if DEMO_MODE + hue_scale.adjustment.value = hue; + lamp.hue = hue_scale.adjustment.value; + + saturation_scale.adjustment.value = saturation; + lamp.saturation = saturation_scale.adjustment.value; + + brightness_scale.adjustment.value = brightness; + lamp.brightness = brightness_scale.adjustment.value; + #else + controller.switch_hsb (hue, saturation, brightness); + + hue_scale.adjustment.value = hue; + saturation_scale.adjustment.value = saturation; + brightness_scale.adjustment.value = brightness; + #endif + }); + + content_area.attach (color_picker, 0, 4, 1, 1); + } } controller.device.notify.connect (update_status); @@ -178,4 +217,8 @@ public class Pages.DevicePage : Pages.AbstractDevicePage { break; } } + + private uint16 remap_value (uint16 value, uint16 in_min, uint16 in_max, uint16 out_min, uint16 out_max) { + return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } } diff --git a/src/philips/hue/BridgeController.vala b/src/philips/hue/BridgeController.vala index 4bc0c4b..b6b8364 100644 --- a/src/philips/hue/BridgeController.vala +++ b/src/philips/hue/BridgeController.vala @@ -274,6 +274,15 @@ public class Philips.Hue.BridgeController { switch_light_state (lamp, state); } + public void switch_light_hsb (Philips.Hue.Lamp lamp, uint16 hue, uint16 saturation, uint16 brightness) { + var state = new Json.Object (); + state.set_int_member ("hue", hue); + state.set_int_member ("sat", saturation); + state.set_int_member ("bri", brightness); + + switch_light_state (lamp, state); + } + public void switch_light_color_temperature (Philips.Hue.Lamp lamp, uint16 color_temperature) { var state = new Json.Object (); state.set_int_member ("ct", (uint16) (1000000 / color_temperature)); diff --git a/src/philips/hue/Controller.vala b/src/philips/hue/Controller.vala index 088c5d0..cffbd32 100644 --- a/src/philips/hue/Controller.vala +++ b/src/philips/hue/Controller.vala @@ -51,6 +51,15 @@ public class Philips.Hue.Controller : Controllers.DeviceController { lamp.brightness = brightness; } + public override void switch_hsb (uint16 hue, uint16 saturation, uint16 brightness) { + var lamp = device as Philips.Hue.Lamp; + controller.switch_light_hsb (lamp, hue, saturation, brightness); + + lamp.hue = hue; + lamp.saturation = saturation; + lamp.brightness = brightness; + } + public override void switch_color_temperature (uint16 color_temperature) { var lamp = device as Philips.Hue.Lamp; controller.switch_light_color_temperature (lamp, color_temperature); diff --git a/src/widgets/ColorPicker.vala b/src/widgets/ColorPicker.vala new file mode 100644 index 0000000..a9c1035 --- /dev/null +++ b/src/widgets/ColorPicker.vala @@ -0,0 +1,88 @@ +public class Widgets.ColorPicker: Gtk.DrawingArea { + private Gdk.RGBA color; + private Gtk.Window window; + private bool dialog_visible; + + public signal void on_color_change (Colors.RGB color); + + public ColorPicker (Gtk.Window window) { + this.window = window; + color.parse ("#4caf50"); + + set_size_request (140, 140); + add_events (Gdk.EventMask.ALL_EVENTS_MASK); + + button_press_event.connect ((event) => { + if (event.button == 1 && !dialog_visible) { + var dialog = new Gtk.ColorSelectionDialog (""); + unowned Gtk.ColorSelection widget = dialog.get_color_selection (); + + widget.current_rgba = color; + + dialog.deletable = false; + dialog.transient_for = window; + dialog_visible = true; + + if (dialog.run () == Gtk.ResponseType.OK) { + if (color != widget.current_rgba) { + color = widget.current_rgba; + + var rgb = new Colors.RGB (); + rgb.red = (uint8) (color.red * 255 + 0.5); + rgb.green = (uint8) (color.green * 255 + 0.5); + rgb.blue = (uint8) (color.blue * 255 + 0.5); + + on_color_change (rgb); + } + } + + dialog_visible = false; + dialog.close (); + } + + return true; + }); + } + + public override bool draw (Cairo.Context ctx) { + int width = get_allocated_width (); + int height = get_allocated_height (); + + // Draw an arc: + double xc = width / 2.0; + double yc = height / 2.0; + double radius = (int.min (width, height) / 2.0); + double angle1 = 0; + double angle2 = 2 * Math.PI; + + int shadow_width = 6; + double shadow_alpha = 1.0; + string shadow_color = "#A9A9A9"; + for (int i = 1; i <= shadow_width; i++) { + ctx.arc (xc, yc, radius - i, angle1, angle2); + Gdk.RGBA c = Gdk.RGBA(); + c.parse (shadow_color); + c.alpha = shadow_alpha / ((shadow_width - i + 1) * (shadow_width - i + 1)); + Gdk.cairo_set_source_rgba (ctx, c); + ctx.stroke (); + } + + ctx.arc (xc, yc, radius - shadow_width, angle1, angle2); + Gdk.cairo_set_source_rgba (ctx, color); + ctx.fill (); + + return true; + } + + public Colors.RGB rgb { + set { + color.parse (value.to_hex ()); + } + } + + public Colors.HSB hsb { + set { + color.parse (new Colors.RGB.from_hsb (value).to_hex ()); + } + } +}