diff --git a/examples/draw.rs b/examples/draw.rs new file mode 100644 index 0000000000000000000000000000000000000000..4dc0174e94d4894f60a45c39994e7ed6b7210bff --- /dev/null +++ b/examples/draw.rs @@ -0,0 +1,210 @@ +// Copyright 2019 The xi-editor Authors. +// +// 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. + +//! An example of an animating widget. + +use std::f64::consts::PI; + +use druid::kurbo::{BezPath, Line}; +use druid::widget::{Flex, Label, WidgetExt}; +use druid::{ + AppLauncher, BoxConstraints, Color, Data, Env, Event, EventCtx, LayoutCtx, Lens, + LinearGradient, PaintCtx, PlatformError, Point, RenderContext, Size, UnitPoint, UpdateCtx, + Vec2, WheelEvent, Widget, WindowDesc, +}; + +use druid_widgets::Dial; +use log::info; + +#[derive(Clone, Data, Lens, Default)] +struct DialLabel { + value: f64, +} + +impl DialLabel { + fn new(text: &str, value: f64) -> impl Widget<DialLabel> { + let text = text.to_string(); + let label = + Label::new(move |data: &DialLabel, _env: &_| format!("{} {:.3}", text, data.value)); + let solid = Color::rgb8(0x3a, 0x3a, 0x3a); + let mut col = Flex::column(); + col.add_child( + Dial::new(value, Size::new(100.0, 100.0)) + .lens(DialLabel::value) + .background(solid.clone()) + .padding(5.0), + 0.0, + ); + col.add_child(label.padding(5.0).background(solid.clone()), 0.0); + col + } +} + +#[derive(Clone, Data, Lens, Default)] +struct Envelope { + attack: DialLabel, + decay: DialLabel, + sustain: DialLabel, + release: DialLabel, +} + +impl Envelope { + fn new() -> impl Widget<Envelope> { + let solid = Color::rgb8(0x3a, 0x3a, 0x3a); + Flex::row() + .with_child( + DialLabel::new("Attack", 0.0) + .lens(Envelope::attack) + .background(solid.clone()) + .padding(5.0), + 0.0, + ) + .with_child( + DialLabel::new("Decay", 0.0) + .lens(Envelope::decay) + .background(solid.clone()) + .padding(5.0), + 0.0, + ) + .with_child( + DialLabel::new("Sustain", 0.0) + .lens(Envelope::sustain) + .background(solid.clone()) + .padding(5.0), + 0.0, + ) + .with_child( + DialLabel::new("Release", 0.0) + .lens(Envelope::release) + .background(solid.clone()) + .padding(5.0), + 0.0, + ) + } +} + +// Draw has no internal state, just reflects the associated type +#[derive(Clone, Data, Lens)] +struct Draw {} + +impl Widget<Envelope> for Draw { + fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut Envelope, _env: &Env) {} + + fn update( + &mut self, + ctx: &mut UpdateCtx, + _old_data: Option<&Envelope>, + _data: &Envelope, + env: &Env, + ) { + //info!("update {:?}", env.get()); + ctx.invalidate(); + } + + fn layout( + &mut self, + _layout_ctx: &mut LayoutCtx, + bc: &BoxConstraints, + _data: &Envelope, + _env: &Env, + ) -> Size { + bc.constrain((100.0, 100.0)) + } + + fn paint(&mut self, paint_ctx: &mut PaintCtx, data: &Envelope, _env: &Env) { + let size = paint_ctx.size(); + info!("paint : {:?}", paint_ctx.size()); + let mut path = BezPath::new(); + let a = data.attack.value; + let d = data.decay.value; + let s = data.sustain.value; + let r = data.release.value; + + let adr_time = a + d + r; + let sustain_time = adr_time / 3.0; // adjust the sustain time + let env_time = adr_time + sustain_time; + let x_scale = size.width / env_time; + let y_scale = size.height; + + path.move_to(Point { + x: 0., + y: size.height, + }); + path.line_to(Point { + x: a * x_scale, + y: 0., + }); + path.line_to(Point { + x: (a + d) * x_scale, + y: size.height - s * y_scale, + }); + path.line_to(Point { + x: (a + d + sustain_time) * x_scale, + y: size.height - s * y_scale, + }); + path.line_to(Point { + x: size.width, + y: size.height, + }); + + paint_ctx.stroke(path, &Color::WHITE, 1.0); + + // if let Some(p) = self.points.iter().next() { + // let mut path = BezPath::new(); + // path.move_to(*p); + + // for p in self.points.iter() { + // path.line_to(*p); + // } + + // paint_ctx.stroke(path, &Color::WHITE, 1.0); + // } + } +} + +#[derive(Clone, Data, Lens)] +struct Adsr { + envelope: Envelope, +} + +impl Adsr { + pub fn new() -> impl Widget<Adsr> { + let solid = Color::rgb8(0x3a, 0x3a, 0x3a); + + Flex::column() + .with_child( + Draw {} + .lens(Adsr::envelope) + .background(solid.clone()) + .padding(5.0), + 0.0, + ) + .with_child(Envelope::new().lens(Adsr::envelope).padding(5.0), 0.0) + } +} + +fn main() -> Result<(), PlatformError> { + AppLauncher::with_window(WindowDesc::new(Adsr::new)) + .use_simple_logger() + .launch(Adsr { + envelope: Envelope { + attack: DialLabel { value: 0.1 }, + decay: DialLabel { value: 0.2 }, + sustain: DialLabel { value: 0.3 }, + release: DialLabel { value: 0.4 }, + }, + })?; + + Ok(()) +}