nana/source/gui/animation.cpp
2015-02-16 06:59:56 +08:00

668 lines
15 KiB
C++

/*
* An Animation Implementation
* Nana C++ Library(http://www.nanapro.org)
* Copyright(C) 2003-2015 Jinhao(cnjinhao@hotmail.com)
*
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* @file: nana/gui/animation.cpp
*/
#include <nana/gui/animation.hpp>
#include <nana/gui/drawing.hpp>
#include <nana/system/timepiece.hpp>
#include <nana/system/platform.hpp>
#include <vector>
#include <list>
#include <algorithm>
#if defined(NANA_MINGW) && defined(STD_THREAD_NOT_SUPPORTED)
#include <nana/std_thread.hpp>
#include <nana/std_mutex.hpp>
#include <nana/std_condition_variable.hpp>
#else
#include <mutex>
#include <condition_variable>
#include <thread>
#endif //NANA_MINGW
namespace nana
{
class animation;
struct output_t
{
drawing::diehard_t diehard;
std::vector<nana::point> points;
output_t()
: diehard(nullptr)
{}
};
struct framebuilder
{
std::size_t length;
std::function<bool(std::size_t, paint::graphics&, nana::size&)> frbuilder;
framebuilder(std::function<bool(std::size_t, paint::graphics&, nana::size&)> f, std::size_t l)
: length(l), frbuilder(std::move(f))
{}
};
struct frame
{
enum class kind
{
oneshot,
framebuilder
};
frame(paint::image img)
: type(kind::oneshot)
{
u.oneshot = new paint::image(std::move(img));
}
frame(std::function<bool(std::size_t, paint::graphics&, nana::size&)> frbuilder, std::size_t length)
: type(kind::framebuilder)
{
u.frbuilder = new framebuilder(std::move(frbuilder), length);
}
frame(const frame& r)
: type(r.type)
{
switch(type)
{
case kind::oneshot:
u.oneshot = new paint::image(*r.u.oneshot);
break;
case kind::framebuilder:
u.frbuilder = new framebuilder(*r.u.frbuilder);
break;
}
}
frame(frame&& r)
: type(r.type)
{
u = r.u;
r.u.oneshot = nullptr;
}
~frame()
{
switch(type)
{
case kind::oneshot:
delete u.oneshot;
break;
case kind::framebuilder:
delete u.frbuilder;
break;
}
}
frame& operator=(const frame& r)
{
if(this != &r)
{
switch(type)
{
case kind::oneshot:
delete u.oneshot;
break;
case kind::framebuilder:
delete u.frbuilder;
break;
}
type = r.type;
switch(type)
{
case kind::oneshot:
u.oneshot = new paint::image(*r.u.oneshot);
break;
case kind::framebuilder:
u.frbuilder = new framebuilder(*r.u.frbuilder);
break;
}
}
return *this;
}
frame& operator=(frame&& r)
{
if(this != &r)
{
switch(type)
{
case kind::oneshot:
delete u.oneshot;
break;
case kind::framebuilder:
delete u.frbuilder;
break;
}
type = r.type;
u = r.u;
r.u.oneshot = nullptr;
}
return *this;
}
std::size_t length() const
{
switch(type)
{
case kind::oneshot:
return 1;
case kind::framebuilder:
return u.frbuilder->length;
}
return 0;
}
//
kind type;
union uframes
{
paint::image * oneshot;
framebuilder * frbuilder;
}u;
};
//class frameset
//struct frameset::impl
struct frameset::impl
{
//Only list whos iterator would not invalided after a insertion.
std::list<frame> frames;
std::list<frame>::iterator this_frame;
std::size_t pos_in_this_frame;
mutable bool good_frame_by_frmbuilder; //It indicates the state of frame whether is valid.
impl()
: this_frame(frames.end()), pos_in_this_frame(0),
good_frame_by_frmbuilder(false)
{}
//Render A frame on the set of windows.
void render_this(std::map<window, output_t>& outs, paint::graphics& framegraph, nana::size& framegraph_dimension) const
{
if(this_frame == frames.end())
return;
frame & frmobj = *this_frame;
switch(frmobj.type)
{
case frame::kind::oneshot:
_m_render(outs, [&frmobj](paint::graphics& tar, const nana::point& pos)
{
frmobj.u.oneshot->paste(tar, pos.x, pos.y);
});
break;
case frame::kind::framebuilder:
good_frame_by_frmbuilder = frmobj.u.frbuilder->frbuilder(pos_in_this_frame, framegraph, framegraph_dimension);
if(good_frame_by_frmbuilder)
{
nana::rectangle r = framegraph_dimension;
_m_render(outs, [&r, &framegraph](paint::graphics& tar, const nana::point& pos) mutable
{
r.x = pos.x;
r.y = pos.y;
tar.bitblt(r, framegraph);
});
}
break;
}
}
//Render a frame on a specified window graph
void render_this(paint::graphics& graph, const nana::point& pos, paint::graphics& framegraph, nana::size& framegraph_dimension, bool rebuild_frame) const
{
if(this_frame == frames.end())
return;
frame & frmobj = *this_frame;
switch(frmobj.type)
{
case frame::kind::oneshot:
frmobj.u.oneshot->paste(graph, pos.x, pos.y);
break;
case frame::kind::framebuilder:
if(rebuild_frame)
good_frame_by_frmbuilder = frmobj.u.frbuilder->frbuilder(pos_in_this_frame, framegraph, framegraph_dimension);
if(good_frame_by_frmbuilder)
{
nana::rectangle r(pos, framegraph_dimension);
graph.bitblt(r, framegraph);
}
break;
}
}
bool eof() const
{
return (frames.end() == this_frame);
}
void next_frame()
{
if(frames.end() == this_frame)
return;
frame & frmobj = *this_frame;
switch(frmobj.type)
{
case frame::kind::oneshot:
++this_frame;
pos_in_this_frame = 0;
break;
case frame::kind::framebuilder:
if(pos_in_this_frame >= frmobj.u.frbuilder->length)
{
pos_in_this_frame = 0;
++this_frame;
}
else
++pos_in_this_frame;
break;
default:
throw std::runtime_error("Nana.GUI.Animation: Bad frame type");
}
}
//Seek to the first frame
void reset()
{
this_frame = frames.begin();
pos_in_this_frame = 0;
}
private:
template<typename Renderer>
void _m_render(std::map<window, output_t>& outs, Renderer renderer) const
{
for(auto & tar: outs)
{
auto graph = API::dev::window_graphics(tar.first);
if(nullptr == graph)
continue;
for(auto & outp : tar.second.points)
renderer(*graph, outp);
API::update_window(tar.first);
}
}
};//end struct frameset::impl
//public:
frameset::frameset()
: impl_(new impl)
{}
void frameset::push_back(paint::image img)
{
bool located = impl_->this_frame != impl_->frames.end();
impl_->frames.emplace_back(std::move(img));
if(false == located)
impl_->this_frame = impl_->frames.begin();
}
void frameset::push_back(framebuilder fb, std::size_t length)
{
impl_->frames.emplace_back(std::move(fb), length);
if(1 == impl_->frames.size())
impl_->this_frame = impl_->frames.begin();
}
//end class frameset
//class animation
class animation::performance_manager
{
public:
struct thread_variable
{
std::mutex mutex;
std::condition_variable condvar;
std::vector<impl*> animations;
std::size_t active; //The number of active animations
std::shared_ptr<std::thread> thread;
std::size_t fps;
double interval; //milliseconds between 2 frames.
double performance_parameter;
};
void insert(impl* p);
void set_fps(impl*, std::size_t new_fps);
void close(impl* p);
bool empty() const;
private:
void _m_perf_thread(thread_variable* thrvar);
private:
mutable std::recursive_mutex mutex_;
std::vector<thread_variable*> threads_;
}; //end class animation::performance_manager
struct animation::impl
{
bool looped{false};
volatile bool paused{true};
std::size_t fps;
std::list<frameset> framesets;
std::map<std::string, branch_t> branches;
std::map<window, output_t> outputs;
paint::graphics framegraph; //framegraph will be created by framebuilder
nana::size framegraph_dimension;
struct state_t
{
std::list<frameset>::iterator this_frameset;
}state;
performance_manager::thread_variable * thr_variable;
static performance_manager * perf_manager;
impl(std::size_t fps)
: fps(fps)
{
state.this_frameset = framesets.begin();
if (!perf_manager)
{
nana::internal_scope_guard lock;
if (!perf_manager)
{
auto pm = new performance_manager;
perf_manager = pm;
}
}
perf_manager->insert(this);
}
~impl()
{
perf_manager->close(this);
{
nana::internal_scope_guard lock;
if(perf_manager->empty())
{
delete perf_manager;
perf_manager = nullptr;
}
}
}
void render_this_specifically(paint::graphics& graph, const nana::point& pos)
{
if(state.this_frameset != framesets.end())
state.this_frameset->impl_->render_this(graph, pos, framegraph, framegraph_dimension, false);
}
void render_this_frame()
{
if(state.this_frameset != framesets.end())
state.this_frameset->impl_->render_this(outputs, framegraph, framegraph_dimension);
}
bool move_to_next()
{
if(state.this_frameset != framesets.end())
{
state.this_frameset->impl_->next_frame();
return (!state.this_frameset->impl_->eof());
}
return false;
}
//Seek to the first frameset
void reset()
{
state.this_frameset = framesets.begin();
if(state.this_frameset != framesets.end())
state.this_frameset->impl_->reset();
}
};//end struct animation::impl
//class animation::performance_manager
void animation::performance_manager::insert(impl* p)
{
std::lock_guard<decltype(mutex_)> lock(mutex_);
for(auto thr : threads_)
{
std::lock_guard<decltype(thr->mutex)> privlock(thr->mutex);
if ((thr->fps == p->fps) && (thr->performance_parameter / (thr->animations.size() + 1) <= 43.3))
{
p->thr_variable = thr;
thr->animations.push_back(p);
return;
}
}
auto thr = new thread_variable;
thr->animations.push_back(p);
thr->performance_parameter = 0.0;
thr->fps = p->fps;
thr->interval = 1000.0 / double(p->fps);
thr->thread = std::make_shared<std::thread>([this, thr]()
{
_m_perf_thread(thr);
});
threads_.push_back(thr);
p->thr_variable = thr;
}
void animation::performance_manager::set_fps(impl* p, std::size_t new_fps)
{
if (p->fps == new_fps)
return;
std::lock_guard<decltype(mutex_)> lock(mutex_);
auto i = std::find(threads_.begin(), threads_.end(), p->thr_variable);
if (i == threads_.end())
return;
p->fps = new_fps;
auto thr = *i;
//Simply modify the fps parameter if the thread just has one animation.
if (thr->animations.size() == 1)
{
thr->fps = new_fps;
thr->interval = 1000.0 / double(new_fps);
return;
}
std::lock_guard<decltype(thr->mutex)> privlock(thr->mutex);
auto u = std::find(thr->animations.begin(), thr->animations.end(), p);
if (u != thr->animations.end())
thr->animations.erase(u);
p->thr_variable = nullptr;
insert(p);
}
void animation::performance_manager::close(impl* p)
{
std::lock_guard<decltype(mutex_)> lock(mutex_);
auto i = std::find(threads_.begin(), threads_.end(), p->thr_variable);
if (i == threads_.end())
return;
auto thr = *i;
std::lock_guard<decltype(thr->mutex)> privlock(thr->mutex);
auto u = std::find(thr->animations.begin(), thr->animations.end(), p);
if(u != thr->animations.end())
thr->animations.erase(u);
}
bool animation::performance_manager::empty() const
{
std::lock_guard<decltype(mutex_)> lock(mutex_);
for(auto thr : threads_)
{
if(thr->animations.size())
return false;
}
return true;
}
void animation::performance_manager::_m_perf_thread(thread_variable* thrvar)
{
nana::system::timepiece tmpiece;
while(true)
{
thrvar->active = 0;
tmpiece.start();
{
std::lock_guard<decltype(thrvar->mutex)> lock(thrvar->mutex);
for(auto ani : thrvar->animations)
{
if(ani->paused)
continue;
ani->render_this_frame();
if(false == ani->move_to_next())
{
if(ani->looped)
{
ani->reset();
++thrvar->active;
}
}
else
++thrvar->active;
}
}
if(thrvar->active)
{
thrvar->performance_parameter = tmpiece.calc();
if(thrvar->performance_parameter < thrvar->interval)
nana::system::sleep(static_cast<unsigned>(thrvar->interval - thrvar->performance_parameter));
}
else
{
//There isn't an active frame, then let the thread
//wait for a signal for an active animation
std::unique_lock<std::mutex> lock(thrvar->mutex);
if(0 == thrvar->active)
thrvar->condvar.wait(lock);
}
}
}
//end class animation::performance_manager
animation::animation(std::size_t fps)
: impl_(new impl(fps))
{
}
void animation::push_back(frameset frms)
{
impl_->framesets.emplace_back(std::move(frms));
if(1 == impl_->framesets.size())
impl_->state.this_frameset = impl_->framesets.begin();
}
/*
void branch(const std::string& name, const frameset& frms)
{
impl_->branches[name].frames = frms;
}
void branch(const std::string& name, const frameset& frms, std::function<std::size_t(const std::string&, std::size_t, std::size_t&)> condition)
{
auto & br = impl_->branches[name];
br.frames = frms;
br.condition = condition;
}
*/
void animation::looped(bool enable)
{
if(impl_->looped != enable)
{
impl_->looped = enable;
if(enable)
{
std::unique_lock<std::mutex> lock(impl_->thr_variable->mutex);
if(0 == impl_->thr_variable->active)
{
impl_->thr_variable->active = 1;
impl_->thr_variable->condvar.notify_one();
}
}
}
}
void animation::play()
{
impl_->paused = false;
std::unique_lock<std::mutex> lock(impl_->thr_variable->mutex);
if(0 == impl_->thr_variable->active)
{
impl_->thr_variable->active = 1;
impl_->thr_variable->condvar.notify_one();
}
}
void animation::pause()
{
impl_->paused = true;
}
void animation::output(window wd, const nana::point& pos)
{
auto & output = impl_->outputs[wd];
if(nullptr == output.diehard)
{
drawing dw(wd);
output.diehard = dw.draw_diehard([this, pos](paint::graphics& tar){
impl_->render_this_specifically(tar, pos);
});
API::events(wd).destroy.connect([this](const arg_destroy& arg){
std::lock_guard<decltype(impl_->thr_variable->mutex)> lock(impl_->thr_variable->mutex);
impl_->outputs.erase(arg.window_handle);
});
}
output.points.push_back(pos);
}
void animation::fps(std::size_t n)
{
if (n == impl_->fps)
return;
impl::perf_manager->set_fps(impl_, n);
}
std::size_t animation::fps() const
{
return impl_->fps;
}
//end class animation
animation::performance_manager * animation::impl::perf_manager;
} //end namespace nana