1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2009-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19
20-module(ex_canvas).
21
22-behaviour(wx_object).
23
24%% Client API
25-export([start/1]).
26
27%% wx_object callbacks
28-export([init/1, terminate/2,  code_change/3,
29	 handle_info/2, handle_call/3, handle_cast/2, handle_event/2, handle_sync_event/3]).
30
31-include_lib("wx/include/wx.hrl").
32
33-record(state,
34	{
35	  parent,
36	  config,
37	  canvas,
38	  bitmap,
39	  overlay,
40	  pos
41	}).
42
43start(Config) ->
44    wx_object:start_link(?MODULE, Config, []).
45
46%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
47init(Config) ->
48    wx:batch(fun() -> do_init(Config) end).
49
50do_init(Config) ->
51    Parent = proplists:get_value(parent, Config),
52    Panel = wxPanel:new(Parent, []),
53
54    %% Setup sizers
55    MainSizer = wxBoxSizer:new(?wxVERTICAL),
56    Sizer = wxStaticBoxSizer:new(?wxVERTICAL, Panel,
57				 [{label, "Various shapes"}]),
58
59    Button = wxButton:new(Panel, ?wxID_ANY, [{label, "Redraw"}]),
60
61    Canvas = wxPanel:new(Panel, [{style, ?wxFULL_REPAINT_ON_RESIZE}]),
62
63    wxPanel:connect(Canvas, paint, [callback]),
64    wxPanel:connect(Canvas, size),
65    wxPanel:connect(Canvas, left_down),
66    wxPanel:connect(Canvas, left_up),
67    wxPanel:connect(Canvas, motion),
68
69    wxPanel:connect(Button, command_button_clicked),
70
71    %% Add to sizers
72    wxSizer:add(Sizer, Button, [{border, 5}, {flag, ?wxALL}]),
73    wxSizer:addSpacer(Sizer, 5),
74    wxSizer:add(Sizer, Canvas, [{flag, ?wxEXPAND},
75				{proportion, 1}]),
76
77    wxSizer:add(MainSizer, Sizer, [{flag, ?wxEXPAND},
78				   {proportion, 1}]),
79
80    wxPanel:setSizer(Panel, MainSizer),
81    wxSizer:layout(MainSizer),
82
83    {W,H} = wxPanel:getSize(Canvas),
84    Bitmap = wxBitmap:new(erlang:max(W,30),erlang:max(30,H)),
85
86    {Panel, #state{parent=Panel, config=Config,
87		   canvas = Canvas, bitmap = Bitmap,
88		   overlay = wxOverlay:new()
89		  }}.
90
91%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
92%% Sync event from callback events, paint event must be handled in callbacks
93%% otherwise nothing will be drawn on windows.
94handle_sync_event(#wx{event = #wxPaint{}}, _wxObj,
95		  #state{canvas=Canvas, bitmap=Bitmap}) ->
96    DC = wxPaintDC:new(Canvas),
97    redraw(DC, Bitmap),
98    wxPaintDC:destroy(DC),
99    ok.
100
101%% Async Events are handled in handle_event as in handle_info
102handle_event(#wx{event = #wxCommand{type = command_button_clicked}},
103	     State = #state{}) ->
104    Image = wxImage:new("image.jpg"),
105    Image2 = wxImage:scale(Image, wxImage:getWidth(Image) div 3,
106			   wxImage:getHeight(Image) div 3),
107    Bmp = wxBitmap:new(Image2),
108    wxImage:destroy(Image),
109    wxImage:destroy(Image2),
110    {W,H} = wxPanel:getSize(State#state.canvas),
111    Positions = lists:map(fun(_) ->
112				  get_pos(W,H)
113			  end, lists:seq(1,(W+H) div 20)),
114    Fun = fun(DC) ->
115		  wxDC:clear(DC),
116		  lists:foreach(fun({X,Y}=Pos) ->
117					wxDC:setBrush(DC, ?wxTRANSPARENT_BRUSH),
118					wxDC:setPen(DC, wxPen:new(?wxBLACK, [{width, 2}])),
119					case X rem 6 of
120					    0 -> wxDC:drawBitmap(DC, Bmp, Pos);
121					    1 -> wxDC:setBrush(DC, ?wxRED_BRUSH),
122						 wxDC:drawRectangle(DC, Pos, {20,20});
123					    2 -> wxDC:setBrush(DC, ?wxBLUE_BRUSH),
124						 wxDC:drawCircle(DC, {X+10, Y+10}, 15);
125					    3 -> wxDC:setPen(DC, wxPen:new({200,200,0,255}, [{width, 4}])),
126						 wxDC:drawLine(DC, Pos, get_pos(W,H));
127					    4 -> wxDC:setBrush(DC, ?wxGREEN_BRUSH),
128						 wxDC:drawEllipse(DC, Pos, {60,20});
129					    _ -> wxDC:drawLabel(DC, "Erlang /", {X,Y,60,20}),
130						 wxDC:drawRotatedText(DC, "OTP", {X+60,Y}, 340.0)
131					end
132				end, Positions)
133	  end,
134    draw(State#state.canvas, State#state.bitmap, Fun),
135    wxBitmap:destroy(Bmp),
136    {noreply, State};
137handle_event(#wx{event = #wxSize{size={W,H}}},
138	     State = #state{bitmap=Prev, canvas=Canvas}) ->
139    if W > 0 andalso H > 0 ->
140	    Bitmap = wxBitmap:new(W,H),
141	    draw(Canvas, Bitmap, fun(DC) -> wxDC:clear(DC) end),
142	    wxBitmap:destroy(Prev),
143	    {noreply, State#state{bitmap = Bitmap}};
144       true ->
145	    {noreply, State}
146    end;
147handle_event(#wx{event = #wxMouse{type=left_down, x=X, y=Y}}, State) ->
148    {noreply, State#state{pos={X,Y}}};
149handle_event(#wx{event = #wxMouse{type=motion, x=X1, y=Y1}},
150	     #state{pos=Start, overlay=Overlay, canvas=Canvas} = State) ->
151    case Start of
152	undefined -> ignore;
153	{X0,Y0} ->
154	    DC = wxClientDC:new(Canvas),
155	    DCO = wxDCOverlay:new(Overlay, DC),
156	    wxDCOverlay:clear(DCO),
157	    wxDC:setPen(DC, ?wxLIGHT_GREY_PEN),
158	    wxDC:setBrush(DC, ?wxTRANSPARENT_BRUSH),
159	    wxDC:drawRectangle(DC, {X0,Y0, X1-X0, Y1-Y0}),
160	    wxDCOverlay:destroy(DCO),
161	    wxClientDC:destroy(DC)
162    end,
163    {noreply, State};
164handle_event(#wx{event = #wxMouse{type=left_up}},
165	     #state{overlay=Overlay, canvas=Canvas} = State) ->
166    DC = wxClientDC:new(Canvas),
167    DCO = wxDCOverlay:new(Overlay, DC),
168    wxDCOverlay:clear(DCO),
169    wxDCOverlay:destroy(DCO),
170    wxClientDC:destroy(DC),
171    wxOverlay:reset(Overlay),
172    {noreply, State#state{pos=undefined}};
173
174handle_event(Ev = #wx{}, State = #state{}) ->
175    demo:format(State#state.config, "Got Event ~p\n", [Ev]),
176    {noreply, State}.
177
178%% Callbacks handled as normal gen_server callbacks
179handle_info(Msg, State) ->
180    demo:format(State#state.config, "Got Info ~p\n", [Msg]),
181    {noreply, State}.
182
183handle_call(shutdown, _From, State=#state{parent=Panel}) ->
184    wxPanel:destroy(Panel),
185    {stop, normal, ok, State};
186handle_call(Msg, _From, State) ->
187    demo:format(State#state.config, "Got Call ~p\n", [Msg]),
188    {reply,{error, nyi}, State}.
189
190handle_cast(Msg, State) ->
191    io:format("Got cast ~p~n",[Msg]),
192    {noreply,State}.
193
194code_change(_, _, State) ->
195    {stop, ignore, State}.
196
197terminate(_Reason, #state{overlay=Overlay}) ->
198    wxOverlay:destroy(Overlay),
199    ok.
200
201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202%% Local functions
203%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
204
205%% Buffered makes it all appear on the screen at the same time
206draw(Canvas, Bitmap, Fun) ->
207    MemoryDC = wxMemoryDC:new(Bitmap),
208    Fun(MemoryDC),
209
210    CDC = wxWindowDC:new(Canvas),
211    wxDC:blit(CDC, {0,0},
212	      {wxBitmap:getWidth(Bitmap), wxBitmap:getHeight(Bitmap)},
213	      MemoryDC, {0,0}),
214    wxWindowDC:destroy(CDC),
215    wxMemoryDC:destroy(MemoryDC).
216
217redraw(DC, Bitmap) ->
218    MemoryDC = wxMemoryDC:new(Bitmap),
219    wxDC:blit(DC, {0,0},
220	      {wxBitmap:getWidth(Bitmap), wxBitmap:getHeight(Bitmap)},
221	      MemoryDC, {0,0}),
222    wxMemoryDC:destroy(MemoryDC).
223
224get_pos(W,H) ->
225    {rand:uniform(W), rand:uniform(H)}.
226