|  | @@ -7,20 +7,66 @@ use ui::ext;
 | 
	
		
			
				|  |  |  use ui::ext::ConnectionExt;
 | 
	
		
			
				|  |  |  use ui::font;
 | 
	
		
			
				|  |  |  use ui::x11;
 | 
	
		
			
				|  |  | -use widgets::{Message, Widget};
 | 
	
		
			
				|  |  | +use widgets::{Message, Update, Widget};
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  use std::rc::Rc;
 | 
	
		
			
				|  |  |  use std::sync::Arc;
 | 
	
		
			
				|  |  |  use std::iter;
 | 
	
		
			
				|  |  |  use std::slice;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#[derive(PartialEq)]
 | 
	
		
			
				|  |  | +enum WidgetStatus {
 | 
	
		
			
				|  |  | +    Okay,
 | 
	
		
			
				|  |  | +    PendingRender,
 | 
	
		
			
				|  |  | +    PendingMeasure,
 | 
	
		
			
				|  |  | +    Measured
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  struct WidgetState {
 | 
	
		
			
				|  |  |      widget: Box<Widget>,
 | 
	
		
			
				|  |  |      position: u16,
 | 
	
		
			
				|  |  | -    width: u16
 | 
	
		
			
				|  |  | +    width: u16,
 | 
	
		
			
				|  |  | +    status: WidgetStatus
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl WidgetState {
 | 
	
		
			
				|  |  | +    fn new(widget: Box<Widget>) -> Self {
 | 
	
		
			
				|  |  | +        WidgetState {
 | 
	
		
			
				|  |  | +            widget: widget,
 | 
	
		
			
				|  |  | +            position: 0,
 | 
	
		
			
				|  |  | +            width: 0,
 | 
	
		
			
				|  |  | +            status: WidgetStatus::PendingMeasure
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn should_render(&self) -> bool {
 | 
	
		
			
				|  |  | +        match self.status {
 | 
	
		
			
				|  |  | +            WidgetStatus::Measured => true,
 | 
	
		
			
				|  |  | +            WidgetStatus::PendingRender => true,
 | 
	
		
			
				|  |  | +            _ => false
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn update(&mut self, status: WidgetStatus) {
 | 
	
		
			
				|  |  | +        if self.status == WidgetStatus::PendingMeasure && status == WidgetStatus::PendingRender {
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        else {
 | 
	
		
			
				|  |  | +            self.status = status;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn measure(&mut self) -> bool {
 | 
	
		
			
				|  |  | +        if self.status == WidgetStatus::PendingMeasure {
 | 
	
		
			
				|  |  | +            self.update(WidgetStatus::Measured);
 | 
	
		
			
				|  |  | +            let old_width = self.width;
 | 
	
		
			
				|  |  | +            self.width = self.widget.width();
 | 
	
		
			
				|  |  | +            if old_width != self.width {
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        false
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      fn contains(&self, x: u16) -> bool {
 | 
	
		
			
				|  |  |          x >= self.position && x < self.position + self.width
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -120,59 +166,75 @@ impl Panel {
 | 
	
		
			
				|  |  |      pub fn handle_event(&mut self, event: Message) -> bool {
 | 
	
		
			
				|  |  |          let finishing = self.finishing;
 | 
	
		
			
				|  |  |          let mut should_exit = false;
 | 
	
		
			
				|  |  | -        if !finishing {
 | 
	
		
			
				|  |  | -            for state in self.widgets_iter() {
 | 
	
		
			
				|  |  | -                should_exit |= state.widget.handle_event(&event);
 | 
	
		
			
				|  |  | +        let mut should_relayout = false;
 | 
	
		
			
				|  |  | +        let mut press_location = None;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if let Message::XcbEvent(ref event) = event {
 | 
	
		
			
				|  |  | +            if finishing && event.response_type() == xcb::DESTROY_NOTIFY {
 | 
	
		
			
				|  |  | +                let event: &xcb::DestroyNotifyEvent = unsafe { xcb::cast_event(event) };
 | 
	
		
			
				|  |  | +                if event.window() == self.window {
 | 
	
		
			
				|  |  | +                    return true
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +            if event.response_type() == xcb::EXPOSE {
 | 
	
		
			
				|  |  | +                should_relayout = true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if event.response_type() == xcb::BUTTON_PRESS {
 | 
	
		
			
				|  |  | +                let event: &xcb::ButtonPressEvent = unsafe { xcb::cast_event(event) };
 | 
	
		
			
				|  |  | +                let x = event.root_x() as u16;
 | 
	
		
			
				|  |  | +                press_location = Some(x);
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        match event {
 | 
	
		
			
				|  |  | -            Message::XcbEvent(event) => {
 | 
	
		
			
				|  |  | -                if event.response_type() == xcb::EXPOSE {
 | 
	
		
			
				|  |  | -                    self.relayout();
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | -                if event.response_type() == xcb::BUTTON_PRESS {
 | 
	
		
			
				|  |  | -                    let event: &xcb::ButtonPressEvent = unsafe { xcb::cast_event(&event) };
 | 
	
		
			
				|  |  | -                    let x = event.root_x() as u16;
 | 
	
		
			
				|  |  | -                    for state in self.widgets_iter() {
 | 
	
		
			
				|  |  | -                        if state.contains(x) {
 | 
	
		
			
				|  |  | -                            let offset_x = x - state.position;
 | 
	
		
			
				|  |  | -                            let event = Message::MousePress(offset_x);
 | 
	
		
			
				|  |  | -                            state.widget.handle_event(&event);
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | +        else if let Message::Quit = event {
 | 
	
		
			
				|  |  | +            self.finish()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if !finishing {
 | 
	
		
			
				|  |  | +            let mut delegate_event = |state: &mut WidgetState, event: &Message| {
 | 
	
		
			
				|  |  | +                match state.widget.handle_event(event) {
 | 
	
		
			
				|  |  | +                    Update::Quit =>
 | 
	
		
			
				|  |  | +                        should_exit = true,
 | 
	
		
			
				|  |  | +                    Update::Relayout =>
 | 
	
		
			
				|  |  | +                        state.update(WidgetStatus::PendingMeasure),
 | 
	
		
			
				|  |  | +                    Update::Rerender =>
 | 
	
		
			
				|  |  | +                        state.update(WidgetStatus::PendingRender),
 | 
	
		
			
				|  |  | +                    Update::Nothing => ()
 | 
	
		
			
				|  |  |                  };
 | 
	
		
			
				|  |  | -                if finishing && event.response_type() == xcb::DESTROY_NOTIFY {
 | 
	
		
			
				|  |  | -                    let event: &xcb::DestroyNotifyEvent = unsafe { xcb::cast_event(&event) };
 | 
	
		
			
				|  |  | -                    if event.window() == self.window {
 | 
	
		
			
				|  |  | -                        return true
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +            if let Some(x) = press_location {
 | 
	
		
			
				|  |  | +                for mut state in self.widgets_iter() {
 | 
	
		
			
				|  |  | +                    if state.contains(x) {
 | 
	
		
			
				|  |  | +                        let offset_x = x - state.position;
 | 
	
		
			
				|  |  | +                        let event = Message::MousePress(offset_x);
 | 
	
		
			
				|  |  | +                        delegate_event(&mut state, &event);
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -            },
 | 
	
		
			
				|  |  | -            Message::Update =>
 | 
	
		
			
				|  |  | -                self.relayout(),
 | 
	
		
			
				|  |  | -            Message::Relayout =>
 | 
	
		
			
				|  |  | -                self.relayout(),
 | 
	
		
			
				|  |  | -            Message::Quit =>
 | 
	
		
			
				|  |  | -                self.finish(),
 | 
	
		
			
				|  |  | -            _ => {}
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            for mut state in self.widgets_iter() {
 | 
	
		
			
				|  |  | +                delegate_event(&mut state, &event);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // check widgets that requested relayout if widths have changed
 | 
	
		
			
				|  |  | +        if self.widgets_iter().any(|state| state.measure()) {
 | 
	
		
			
				|  |  | +            should_relayout = true;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if should_relayout {
 | 
	
		
			
				|  |  | +            self.relayout();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        else {
 | 
	
		
			
				|  |  | +            self.rerender();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          should_exit
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      pub fn add_left_widget(&mut self, widget: Box<Widget>) {
 | 
	
		
			
				|  |  | -        self.left_widgets.push(WidgetState{
 | 
	
		
			
				|  |  | -            widget: widget,
 | 
	
		
			
				|  |  | -            position: 0,
 | 
	
		
			
				|  |  | -            width: 0
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | +        self.left_widgets.push(WidgetState::new(widget));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      pub fn add_right_widget(&mut self, widget: Box<Widget>) {
 | 
	
		
			
				|  |  | -        self.right_widgets.push(WidgetState{
 | 
	
		
			
				|  |  | -            widget: widget,
 | 
	
		
			
				|  |  | -            position: 0,
 | 
	
		
			
				|  |  | -            width: 0
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | +        self.right_widgets.push(WidgetState::new(widget));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      pub fn relayout(&mut self) {
 | 
	
	
		
			
				|  | @@ -185,30 +247,48 @@ impl Panel {
 | 
	
		
			
				|  |  |          );
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // measure
 | 
	
		
			
				|  |  | +        let mut total_width = 0;
 | 
	
		
			
				|  |  | +        for state in self.widgets_iter() {
 | 
	
		
			
				|  |  | +            state.measure();
 | 
	
		
			
				|  |  | +            if !state.widget.fit_width() {
 | 
	
		
			
				|  |  | +                total_width += state.width;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        let margin = self.width.saturating_sub(total_width);
 | 
	
		
			
				|  |  | +        for state in self.widgets_iter() {
 | 
	
		
			
				|  |  | +            if state.widget.fit_width() {
 | 
	
		
			
				|  |  | +                state.width = margin;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // position
 | 
	
		
			
				|  |  |          let mut left_pos = 0;
 | 
	
		
			
				|  |  |          for state in self.left_widgets.iter_mut() {
 | 
	
		
			
				|  |  |              state.position = left_pos;
 | 
	
		
			
				|  |  | -            state.width = state.widget.width();
 | 
	
		
			
				|  |  |              left_pos += state.width;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          let mut right_pos = self.width;
 | 
	
		
			
				|  |  |          for state in self.right_widgets.iter_mut() {
 | 
	
		
			
				|  |  | -            state.width = state.widget.width();
 | 
	
		
			
				|  |  |              right_pos -= state.width;
 | 
	
		
			
				|  |  |              state.position = right_pos;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        let margin = right_pos.saturating_sub(left_pos);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          // render
 | 
	
		
			
				|  |  |          for state in self.widgets_iter() {
 | 
	
		
			
				|  |  | -            let width = if state.widget.fit_width() {
 | 
	
		
			
				|  |  | -                margin
 | 
	
		
			
				|  |  | +            state.widget.render(state.position, state.width);
 | 
	
		
			
				|  |  | +            state.update(WidgetStatus::Okay);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        self.conn.flush();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn rerender(&mut self) {
 | 
	
		
			
				|  |  | +        let context = self.make_draw_context();
 | 
	
		
			
				|  |  | +        for state in self.widgets_iter() {
 | 
	
		
			
				|  |  | +            if state.should_render() {
 | 
	
		
			
				|  |  | +                context.draw_bg(state.position, state.width);
 | 
	
		
			
				|  |  | +                state.widget.render(state.position, state.width);
 | 
	
		
			
				|  |  | +                state.update(WidgetStatus::Okay);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            else {
 | 
	
		
			
				|  |  | -                state.width
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | -            state.widget.render(state.position, width);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          self.conn.flush();
 | 
	
		
			
				|  |  |      }
 |